Bite 30I.1 — Global Administration Control Plane - RodrigoMattosoSilveira/EnterpriseRemoteSystems GitHub Wiki

Bite 30I.1 — Manual Promotion Test Suite

Issue: Bite #30I.1 — Global Administration Control Plane + Tenant-Driven Person Reactivation
Branch: 754-bite-30i1-global-administration-control-plane
Committed SHA under test: b773bb248fba779e59b8d525f09d0c426e2856b0
Promotion target: Bite #30I integration branch
Automated prerequisite: local and CI suites already green at this SHA


Promotion decision rule

Promote Bite 30I.1 only if every BLOCKER test passes.

Any of the following is an immediate promotion blocker:

  • an Application Administrator acquires standing Tenant business-data access;
  • a Tenant Administrator can override an application-level security suspension;
  • operational Person deactivation leaves any Tenant Membership active;
  • Tenant reactivation silently reactivates another Tenant Membership;
  • a previous delegated Role Grant becomes effective merely because the Person was reactivated;
  • a security-suspended Account becomes usable before Application Administrator review;
  • Application Administrator UI exposes Tenant business-data navigation as though the global Actor had standing Tenant authority;
  • a second-Tenant authentication enablement creates a second global Account or changes the existing Account credentials.

Record every failure with screenshots, browser DevTools Network/Console evidence when relevant, and the SQL verification output shown in this document.


0. Test environment and fixtures

0.1 Validate the committed branch

From the repository root:

git branch --show-current
git rev-parse HEAD
git status --short

Expected:

754-bite-30i1-global-administration-control-plane
b773bb248fba779e59b8d525f09d0c426e2856b0

git status --short should be empty before preparing fixtures.

0.2 Confirm migration 000064 has run

Stop the backend before accessing the SQLite database directly.

sqlite3 backend/data/app.db "PRAGMA table_info(global_people);" | grep operational_active
sqlite3 backend/data/app.db "PRAGMA table_info(auth_user_accounts);" | grep security_suspended
sqlite3 backend/data/app.db "PRAGMA table_info(authz_actor_role_grants);" | grep lifecycle_suspended

Expected: all three columns exist.

0.3 Create the isolated manual-promotion fixture

With the backend stopped:

python3 ~/Downloads/seed-bite30i1-manual-promotion.py

The script creates a timestamped backup of backend/data/app.db before writing.

The suite below assumes the default fixture batch promotion1.

Seeded credentials

All fixture Accounts use:

Password: Manual-30C-Password!
Identity Login Purpose
Application Administrator [email protected] Global control plane and security suspension review
Tenant Administrator A [email protected] Tenant A lifecycle/onboarding
Tenant Administrator B [email protected] Tenant B lifecycle/onboarding
Returning Person [email protected] Two-Tenant deactivation/reactivation + old Role Grants
Security Candidate [email protected] Security suspension boundary
New-Tenant Candidate [email protected] Add-existing-Person reactivation into a new Tenant
Authentication-Reuse Person [email protected] Second-Tenant Account reuse with a different Tenant-local email

Fixture Tenants

Tenant A ID: manual30i1-tenant-7e01cb784ab4
Tenant A:    30I.1 Manual Tenant A (promotion1)

Tenant B ID: manual30i1-tenant-f5b348148e1b
Tenant B:    30I.1 Manual Tenant B (promotion1)

Important Person IDs

Returning Person — Tenant A:
manual30i1-person-69a8e58bebaa

Returning Person — Tenant B:
manual30i1-person-b9aa9d27961d

Security Candidate — Tenant A:
manual30i1-person-7437c10e035b

New-Tenant Candidate — Tenant A:
manual30i1-person-4ab57983d3b9

Authentication-Reuse — Tenant A:
manual30i1-person-2cc0274b5ba4

Authentication-Reuse — Tenant B:
manual30i1-person-6271e5e24b85

Restart the backend and frontend normally after seeding.

Use a fresh/incognito browser context when changing identities, or explicitly sign out first.


1. Application Administrator lands in the global control plane — BLOCKER

Purpose

Verify that APPLICATION_ADMIN is an application-scoped global Actor whose normal workspace is the global Tenant catalog, not a Tenant workspace.

Steps

  • Open /login.
  • Sign in as [email protected].
  • Verify the browser lands at /admin/tenants.
  • Verify the page heading is Tenants.
  • Verify the header exposes global administration links:
    • Authentication
    • Authorization
    • Audit logs
  • Verify Back to People is not shown on the Tenants landing page for this Actor.
  • Verify there is no normal Tenant-workspace selector that lets the Application Administrator enter Tenant A or Tenant B as a standing Tenant Actor.

DevTools verification

Browser Console:

const r = await fetch('/api/v1/authz/current-actor', {
  credentials: 'include',
  headers: {
    'X-Tenant-ID': '*'
  }
});
console.log(r.status, await r.json());

[✔︎] Expected data includes:

scope: APPLICATION
tenantId: *
roleCodes: [APPLICATION_ADMIN]

[✔︎] Expected effective permissions are exactly:

authz.self.read
authz.read
authz.manage
tenants.read
tenants.create
tenants.update

PASS

[✔︎] Application Administrator operates at tenantId="*", lands on /admin/tenants, and has only global control-plane navigation/authority.


2. APPLICATION_ADMIN permission allowlist and migration guard — BLOCKER

Stop the backend only if you need a completely quiet database while running these read-only SQL checks.

sqlite3 -header -column backend/data/app.db <<'SQL'
SELECT rp.permission_code
FROM authz_role_permissions rp
JOIN authz_roles r ON r.id = rp.role_id
WHERE r.code = 'APPLICATION_ADMIN'
ORDER BY rp.permission_code;

SELECT name
FROM sqlite_master
WHERE type = 'trigger'
  AND name IN (
    'trg_application_admin_control_plane_permission_insert',
    'trg_application_admin_control_plane_permission_update'
  )
ORDER BY name;
SQL

[✔︎] Expected permissions — exactly six:

authz.manage
authz.read
authz.self.read
tenants.create
tenants.read
tenants.update

[✔︎] Expected triggers:

trg_application_admin_control_plane_permission_insert
trg_application_admin_control_plane_permission_update

PASS

[✔︎] No Tenant business-data permission such as people.read, expenses.read, collaborators.read, etc. is assigned to APPLICATION_ADMIN.


3. Global Tenant catalog create/update does not confer Tenant workspace authority — BLOCKER

Remain signed in as the fixture Application Administrator.

Steps

  • On /admin/tenants, create a temporary Tenant using a unique code, for example M30I1_UI_<your initials/time>.
  • Give it a recognizable name such as 30I.1 UI Promotion Check.
  • Keep Activate immediately selected.
  • Click Create Tenant.
  • Verify the success message and Tenant catalog entry.
  • Open the Tenant detail page.
  • Change its description and save.
  • Return to /admin/tenants and verify the updated metadata.
  • Verify creation/update did not add that Tenant as a selectable Tenant workspace for the Application Administrator.

PASS

[x] Application Administrator can administer Tenant metadata but does not gain Tenant business-data authority as a consequence.


4. Audit Log Viewer must not advertise Tenant data-plane navigation — BLOCKER

This is an explicit manual promotion check because the global control-plane Actor must not be presented as though it has standing Tenant business authority.

Steps

  • While signed in as the Application Administrator, open /admin/audit-logs.
  • Verify the page heading is Audit Log Viewer.
  • Inspect the header/navigation links.

Required PASS condition

The Application Administrator Audit Log Viewer must not expose shortcuts that imply ordinary Tenant business-data access, including:

  • Current Account Settings — must not be visible
  • Gold Prices — must not be visible
  • [ x Price List — must not be visible
  • Back to People — must not be visible

[x] The global Authorization link is appropriate.

FAIL / promotion blocker

If any of those Tenant data-plane shortcuts are visible to the Application Administrator, record the page screenshot and stop promotion until corrected, even if the destination eventually rejects access.


5. Application Administrator cannot force Tenant business-data access — BLOCKER

Remain signed in as the Application Administrator.

DevTools test

Browser Console:

const TENANT_A = 'manual30i1-tenant-7e01cb784ab4';
const response = await fetch('/api/v1/people?page=1&pageSize=5', {
  credentials: 'include',
  headers: {'X-Tenant-ID': TENANT_A}
});
console.log(response.status, await response.json());

[✔︎] Expected:

HTTP 403
error.code = tenant_actor_unavailable

Repeat against another Tenant business-data route if desired, for example:

const response2 = await fetch('/api/v1/collaborators?page=1&pageSize=5', {
  credentials: 'include',
  headers: {'X-Tenant-ID': TENANT_A}
});
console.log(response2.status, await response2.json());

Expected: the global Actor is not converted into a Tenant Actor merely by supplying X-Tenant-ID.

PASS

[✔︎] No standing Tenant business-data access exists.


6. Tenant Administrators remain Tenant-scoped — BLOCKER

Tenant A

  • Sign out.
  • Sign in as [email protected].
  • Open /people.
  • Verify Tenant A People are accessible.
  • Open Browser Console and run:
const tenantId = localStorage.getItem('ers.auth.selectedTenantId');

const r = await fetch('/api/v1/authz/current-actor', {
  credentials: 'include',
  headers: {
    'X-Tenant-ID': tenantId
  }
});

console.log(r.status, await r.json());

[✔︎] Expected:

scope: TENANT
tenantId: manual30i1-tenant-7e01cb784ab4
roleCodes includes TENANT_ADMIN

Tenant B

scope: TENANT
tenantId: manual30i1-tenant-f5b348148e1b
roleCodes includes TENANT_ADMIN

PASS

[✔︎] Each Tenant Administrator receives only its own Tenant context.


7. Second-Tenant Authentication provisioning reuses one global Account — BLOCKER

This verifies the final local-check regression fix: Tenant B's local Person email deliberately differs from the global Authentication Account login.

Initial state

Global Account login:
[email protected]

Tenant B local Person email:
[email protected]

The fixture deliberately has no Tenant B Account-bound Actor yet.

Steps

  • Sign in as Tenant Administrator B.
  • Open:
/people/manual30i1-person-6271e5e24b85
  • Scroll to Authentication.
  • Verify status is Not enabled for this tenant.
  • Verify the Account login shown is [email protected] — not the Tenant B local email.
  • Verify no new temporary password is required; the page should explain that existing Account credentials will not be changed.
  • Click Enable Authentication.
  • Verify success states that authentication is enabled for this Tenant and Account credentials were not changed.

SQL verification

sqlite3 -header -column backend/data/app.db <<'SQL'
SELECT id, login, active, security_suspended, password_hash
FROM auth_user_accounts
WHERE login = '[email protected]';

SELECT aa.account_id, aa.actor_id, aa.scope_type, aa.tenant_id, aa.membership_id, aa.is_primary
FROM auth_account_actors aa
JOIN auth_user_accounts a ON a.id = aa.account_id
WHERE a.login = '[email protected]'
ORDER BY aa.tenant_id;
SQL

Expected:

  • exactly one global Authentication Account row;

  • Account remains active and not security-suspended;

  • password hash is unchanged from the seeded Account;

  • two Tenant Actor bindings now exist — Tenant A and Tenant B;

  • Tenant B binding uses Tenant B's exact selected Person/Membership, despite its different local email.

  • Sign out and sign in as [email protected] using Manual-30C-Password! to prove credentials were not replaced.

PASS

[x] Second-Tenant enablement reuses the same global Account and does not require Tenant-local email equality.


8. Returning Person begins with two active Tenant relationships and delegated roles

This is the destructive lifecycle sequence. Perform Tests 8–12 in order using the same fixture.

SQL baseline

sqlite3 -header -column backend/data/app.db <<'SQL'
SELECT gp.id, gp.nickname, gp.operational_active
FROM global_people gp
WHERE gp.nickname = '30I1 Returning Person';

SELECT m.id AS membership_id, m.tenant_id, s.code AS membership_status, p.id AS tenant_person_id
FROM person_tenant_memberships m
JOIN global_people gp ON gp.id = m.person_id
JOIN reference_data s ON s.id = m.status_id AND s.tenant_id = m.tenant_id
LEFT JOIN people p ON p.id = m.legacy_person_id
WHERE gp.nickname = '30I1 Returning Person'
ORDER BY m.tenant_id;

SELECT a.actor_key, a.active AS actor_active, r.code AS role_code,
       g.tenant_id, g.active AS grant_active, g.lifecycle_suspended
FROM authz_actor_role_grants g
JOIN authz_actors a ON a.id = g.actor_id
JOIN authz_roles r ON r.id = g.role_id
WHERE a.actor_key LIKE 'manual30i1-promotion1-returnee-%'
ORDER BY g.tenant_id;
SQL

Expected:

  • global operational_active = 1;
  • both Memberships ACTIVE;
  • both Tenant Actors active;
  • EXPENSE_OPERATOR is assigned/effective in both Tenants (active=1, lifecycle_suspended=0).

9. Operational deactivation cascades across every Tenant — BLOCKER

Steps

  • Sign in as Tenant Administrator A.
  • Open:
/people/manual30i1-person-69a8e58bebaa
  • On the Personal tab, change Status from Active to Inactive.
  • Save the Person.
  • Verify the update succeeds.

SQL verification

sqlite3 -header -column backend/data/app.db <<'SQL'
SELECT gp.operational_active
FROM global_people gp
WHERE gp.nickname = '30I1 Returning Person';

SELECT m.tenant_id, s.code AS membership_status
FROM person_tenant_memberships m
JOIN global_people gp ON gp.id = m.person_id
JOIN reference_data s ON s.id = m.status_id AND s.tenant_id = m.tenant_id
WHERE gp.nickname = '30I1 Returning Person'
ORDER BY m.tenant_id;

SELECT a.actor_key, a.active AS actor_active,
       r.code AS role_code, g.tenant_id,
       g.active AS grant_active, g.lifecycle_suspended
FROM authz_actor_role_grants g
JOIN authz_actors a ON a.id = g.actor_id
JOIN authz_roles r ON r.id = g.role_id
WHERE a.actor_key LIKE 'manual30i1-promotion1-returnee-%'
ORDER BY g.tenant_id;

SELECT active, security_suspended
FROM auth_user_accounts
WHERE login = '[email protected]';
SQL

[✔︎] Expected:

global Person operational_active = 0
Tenant A Membership = INACTIVE
Tenant B Membership = INACTIVE
Tenant A Actor active = 0
Tenant B Actor active = 0
Account active = 0
Account security_suspended = 0
both EXPENSE_OPERATOR grants: active = 1, lifecycle_suspended = 1

The critical 30H invariant is visible here: Role Grants are retained, not revoked.

Login verification

Your Person is operationally inactive. Contact a Tenant Administrator to reactivate you for their Tenant.

  • Verify Request reactivation is not offered for this operational inactivity state.

PASS

[✔︎] Deactivation removes effective access in every Tenant without deleting historical authorization assignments.


10. Tenant A reactivation restores only Tenant A baseline access — BLOCKER

Steps

  • Sign in as Tenant Administrator A.
  • Open the Returning Person in Tenant A:
/people/manual30i1-person-69a8e58bebaa
  • Scroll to Authentication.
  • Verify the card says Status: Operationally inactive.
  • Verify the explanatory text says only this Tenant's Membership/baseline access will be restored and previous delegated roles remain suspended.
  • Click Reactivate Person for this Tenant.
  • Verify success:

Person and Authentication Account reactivated for this Tenant. Previous delegated privileges remain suspended and must be explicitly granted again.

SQL verification

sqlite3 -header -column backend/data/app.db <<'SQL'
SELECT gp.operational_active
FROM global_people gp
WHERE gp.nickname = '30I1 Returning Person';

SELECT m.tenant_id, s.code AS membership_status
FROM person_tenant_memberships m
JOIN global_people gp ON gp.id = m.person_id
JOIN reference_data s ON s.id = m.status_id AND s.tenant_id = m.tenant_id
WHERE gp.nickname = '30I1 Returning Person'
ORDER BY m.tenant_id;

SELECT a.actor_key, a.active AS actor_active,
       r.code AS role_code, g.tenant_id,
       g.active AS grant_active, g.lifecycle_suspended
FROM authz_actor_role_grants g
JOIN authz_actors a ON a.id = g.actor_id
JOIN authz_roles r ON r.id = g.role_id
WHERE a.actor_key LIKE 'manual30i1-promotion1-returnee-%'
ORDER BY g.tenant_id;

SELECT active, security_suspended
FROM auth_user_accounts
WHERE login = '[email protected]';
SQL

[✔︎] Expected:

Person operational_active = 1
Tenant A Membership = ACTIVE
Tenant B Membership = INACTIVE
Tenant A Actor active = 1
Tenant B Actor active = 0
Account active = 1
security_suspended = 0
both historical EXPENSE_OPERATOR grants remain lifecycle_suspended = 1

Login / authorization verification

  • Sign out and sign in as the Returning Person.
  • Verify sign-in succeeds.
  • Browser Console:
const tenantId = localStorage.getItem('ers.auth.selectedTenantId');

console.log('Selected Tenant:', tenantId);

const r = await fetch('/api/v1/authz/current-actor', {
  credentials: 'include',
  headers: {
    'X-Tenant-ID': tenantId
  }
});

console.log(r.status, await r.json());

Expected:

  • Tenant A is the usable Tenant context;
  • EXPENSE_OPERATOR is not an effective role;
  • the Person has only baseline/self-service authority appropriate to a newly onboarded Person.

PASS

[✔︎] Reactivation restores only Tenant A baseline access and does not resurrect old delegated privileges.


11. Explicit re-grant restores only the selected Tenant role — BLOCKER

Steps

  • Sign in as Tenant Administrator A.
  • Open Authorization → Tenant Authorization.
  • Search for [email protected] or Actor Key manual30i1-promotion1-returnee-a.
  • Verify:
    • Actor ACTIVE
    • Membership ACTIVE
    • Expenses Operator shows Not granted even though the historical grant row still exists.
  • Click Grant for Expenses Operator.
  • Verify it changes to Granted.

SQL verification

sqlite3 -header -column backend/data/app.db <<'SQL'
SELECT a.actor_key, r.code AS role_code, g.tenant_id,
       g.active, g.lifecycle_suspended
FROM authz_actor_role_grants g
JOIN authz_actors a ON a.id = g.actor_id
JOIN authz_roles r ON r.id = g.role_id
WHERE a.actor_key LIKE 'manual30i1-promotion1-returnee-%'
ORDER BY g.tenant_id;
SQL

Expected now:

Tenant A EXPENSE_OPERATOR: active=1, lifecycle_suspended=0
Tenant B EXPENSE_OPERATOR: active=1, lifecycle_suspended=1

PASS

[✔︎] Only an explicit Tenant A grant restores Tenant A delegated authority.


12. Tenant B can later reactivate its own Membership without restoring its old role — BLOCKER

Steps

  • Sign in as Tenant Administrator B.
  • Open:
/people/manual30i1-person-b9aa9d27961d
  • Scroll to Authentication.
  • Because Tenant A already reactivated the global Person, verify the status is now:

Status: Inactive in this Tenant

  • Click Reactivate Person for this Tenant.
  • Verify successful reactivation.

SQL verification

sqlite3 -header -column backend/data/app.db <<'SQL'
SELECT m.tenant_id, s.code AS membership_status
FROM person_tenant_memberships m
JOIN global_people gp ON gp.id = m.person_id
JOIN reference_data s ON s.id = m.status_id AND s.tenant_id = m.tenant_id
WHERE gp.nickname = '30I1 Returning Person'
ORDER BY m.tenant_id;

SELECT a.actor_key, a.active AS actor_active, r.code,
       g.tenant_id, g.active, g.lifecycle_suspended
FROM authz_actor_role_grants g
JOIN authz_actors a ON a.id = g.actor_id
JOIN authz_roles r ON r.id = g.role_id
WHERE a.actor_key LIKE 'manual30i1-promotion1-returnee-%'
ORDER BY g.tenant_id;
SQL

Expected:

Tenant A Membership = ACTIVE
Tenant B Membership = ACTIVE
Tenant A explicit EXPENSE_OPERATOR grant remains effective
Tenant B historical EXPENSE_OPERATOR grant remains lifecycle_suspended=1

PASS

[✔︎] Tenant B reactivation does not affect Tenant A's state and does not silently restore Tenant B's previous privilege.


13. Adding an operationally inactive Person to a new Tenant routes through the reactivation transaction — BLOCKER

The New-Tenant Candidate initially belongs only to Tenant A.

Part A — operationally deactivate in Tenant A

  • Sign in as Tenant Administrator A.
  • Open:
/people/manual30i1-person-4ab57983d3b9
  • Change Status to Inactive and save.
  • Confirm the Account can no longer sign in and gets the operational-inactivity message.

Part B — onboard into Tenant B

  • Sign in as Tenant Administrator B.
  • Open:
/people/add-existing
  • Search for 30I1 New Tenant Candidate or CPF 48391606864.
  • Select the Person.
  • Choose Tenant status Active.
  • Click Add to this tenant.
  • Verify the membership-added success/flash message.

SQL verification

sqlite3 -header -column backend/data/app.db <<'SQL'
SELECT gp.id, gp.operational_active
FROM global_people gp
WHERE gp.nickname = '30I1 New Tenant Candidate';

SELECT m.id, m.tenant_id, s.code AS status, m.legacy_person_id
FROM person_tenant_memberships m
JOIN global_people gp ON gp.id = m.person_id
JOIN reference_data s ON s.id=m.status_id AND s.tenant_id=m.tenant_id
WHERE gp.nickname='30I1 New Tenant Candidate'
ORDER BY m.tenant_id;

SELECT aa.account_id, aa.actor_id, aa.tenant_id, aa.membership_id, az.active AS actor_active
FROM auth_account_actors aa
JOIN auth_user_accounts au ON au.id=aa.account_id
JOIN authz_actors az ON az.id=aa.actor_id
WHERE au.login='[email protected]'
ORDER BY aa.tenant_id;
SQL

Expected:

global operational_active = 1
Tenant A Membership remains INACTIVE
Tenant B Membership = ACTIVE
Tenant B Account-bound Actor exists and is ACTIVE
Account becomes usable

PASS

[x] There is no intermediate/lasting invalid state of Person INACTIVE + new Tenant Membership ACTIVE; onboarding uses the controlled reactivation semantics atomically.


14. Application security suspension cannot be overridden by Tenant Administrator — BLOCKER

Use the Security Candidate and perform this sequence in order.

Part A — operationally deactivate the Person

  • Sign in as Tenant Administrator A.
  • Open:
/people/manual30i1-person-7437c10e035b
  • Set Status to Inactive and save.

Part B — impose application-level security suspension

  • Sign in as the Application Administrator.
  • Open /admin/authentication.
  • Find Account:
[email protected]
  • Click Security suspend.
  • Verify the card now identifies the Account as security-suspended and offers Clear security suspension.

Part C — Tenant Administrator may not reactivate

  • Sign in as Tenant Administrator A.
  • Reopen the Security Candidate Person.
  • Scroll to Authentication.
  • Verify:

Status: Application security suspension

  • Verify Reactivate Person for this Tenant is not shown.
  • Verify Request Application Administrator Review is shown.

SQL verification

sqlite3 -header -column backend/data/app.db <<'SQL'
SELECT gp.operational_active, a.active AS account_active, a.security_suspended
FROM global_people gp
JOIN auth_account_people ap ON ap.person_id=gp.id
JOIN auth_user_accounts a ON a.id=ap.account_id
WHERE gp.nickname='30I1 Security Candidate';
SQL

Expected:

operational_active = 0
account_active = 0
security_suspended = 1

PASS

[✔︎] Tenant Administrator cannot override the application security boundary.


15. Security-suspended login and reactivation review remain Application Administrator controlled — BLOCKER

User login

Your Authentication Account is security-suspended. Request Application Administrator review to regain access.

  • Verify Reset a password is not offered in this inactive security state.
  • Verify Request reactivation is available.

You may use either the user's Request reactivation button or the Tenant Administrator's Request Application Administrator Review button from Test 14. Use one request, not both.

  • Submit the reactivation request.

Application Administrator landing-page alert

  • Sign in as the Application Administrator.
  • Verify /admin/tenants shows the Pending account reactivation requests region.
  • Verify the alert has a red border.
  • Verify the text indicates an authentication request is awaiting Application Administrator review.
  • Click Review requests.
  • Verify navigation to /admin/authentication#account-reactivation-requests.
  • Locate the Security Candidate request.
  • Enter a review reason, for example:
30I.1 manual promotion security-suspension review
  • Click Approve reactivation.
  • Verify the request is removed from Pending / pending count decrements.

Critical post-approval state

Approval clears only the application security suspension. It must not bypass operational Tenant reactivation.

sqlite3 -header -column backend/data/app.db <<'SQL'
SELECT gp.operational_active, a.active AS account_active, a.security_suspended
FROM global_people gp
JOIN auth_account_people ap ON ap.person_id=gp.id
JOIN auth_user_accounts a ON a.id=ap.account_id
WHERE gp.nickname='30I1 Security Candidate';
SQL

[x] Expected immediately after Application Administrator approval:

operational_active = 0
security_suspended = 0
account_active = 0
  • Attempting to sign in still does not provide Tenant access.
  • Sign in as Tenant Administrator A.
  • Reopen the Security Candidate.
  • Verify Reactivate Person for this Tenant is now available.
  • Click it.
  • Verify the Account/Person becomes usable for Tenant A.
  • Sign in as the Security Candidate successfully.

PASS

[✔︎] Application Administrator clears global security suspension; Tenant Administrator separately owns operational Tenant reactivation.


16. Security Candidate's previous delegated privilege is not restored after reactivation — BLOCKER

The fixture initially gave the Security Candidate an EARNINGS_OPERATOR Role Grant.

After Test 15 completes:

sqlite3 -header -column backend/data/app.db <<'SQL'
SELECT a.actor_key, r.code AS role_code, g.active, g.lifecycle_suspended
FROM authz_actor_role_grants g
JOIN authz_actors a ON a.id=g.actor_id
JOIN authz_roles r ON r.id=g.role_id
WHERE a.actor_key='manual30i1-promotion1-security-candidate-a';
SQL

Expected:

EARNINGS_OPERATOR
active = 1
lifecycle_suspended = 1
  • In Tenant A Tenant Authorization, locate the Security Candidate.
  • Verify Earnings Operator is presented as Not granted until explicitly granted again.

PASS

[✔︎] Operational reactivation does not silently resurrect pre-deactivation operator privileges.


17. Application Administrator Account controls are security controls, not operational Tenant-reactivation controls — BLOCKER

  • Sign in as the Application Administrator.
  • Open /admin/authentication.
  • Inspect ordinary Account cards.
  • Verify the lifecycle action language is ([email protected]):
    • Security suspend
    • Clear security suspension
  • Verify the page explains:

Security suspension is application-global. Operational Person reactivation belongs to a Tenant Administrator.

  • Verify operationally inactive Accounts are distinguishable from security-suspended Accounts.
  • Verify the Application Administrator does not receive a control that directly reactivates a Person's Tenant Membership.

PASS

[✔︎] Global security state and Tenant operational lifecycle are visibly and functionally distinct.


18. Final database invariant sweep — BLOCKER

Run after completing Tests 1–17.

sqlite3 -header -column backend/data/app.db <<'SQL'
-- APPLICATION_ADMIN exact permission set.
SELECT rp.permission_code
FROM authz_role_permissions rp
JOIN authz_roles r ON r.id=rp.role_id
WHERE r.code='APPLICATION_ADMIN'
ORDER BY rp.permission_code;

-- No ACTIVE Membership may exist for a globally operationally inactive Person.
SELECT gp.id, gp.nickname, m.id AS membership_id, m.tenant_id, s.code AS status
FROM global_people gp
JOIN person_tenant_memberships m ON m.person_id=gp.id
JOIN reference_data s ON s.id=m.status_id AND s.tenant_id=m.tenant_id
WHERE gp.operational_active=0
  AND s.type='person_status'
  AND s.code='ACTIVE';

-- No lifecycle-suspended grant may be interpreted here as deleted/revoked.
SELECT a.actor_key, r.code, g.tenant_id, g.active, g.lifecycle_suspended
FROM authz_actor_role_grants g
JOIN authz_actors a ON a.id=g.actor_id
JOIN authz_roles r ON r.id=g.role_id
WHERE a.actor_key LIKE 'manual30i1-promotion1-%'
  AND g.lifecycle_suspended=1
ORDER BY a.actor_key, r.code, g.tenant_id;

-- Account security/operational states for fixture People.
SELECT gp.nickname, gp.operational_active,
       au.login, au.active AS account_active, au.security_suspended
FROM global_people gp
JOIN auth_account_people ap ON ap.person_id=gp.id
JOIN auth_user_accounts au ON au.id=ap.account_id
WHERE gp.nickname LIKE '30I1 %'
ORDER BY gp.nickname;

PRAGMA foreign_key_check;
SQL

Required results:

  • APPLICATION_ADMIN still has exactly the six control-plane permissions.
  • The operational_active=0 AND Membership ACTIVE query returns zero rows.
  • Historical suspended Role Grants remain present with active=1 and lifecycle_suspended=1 unless you explicitly re-granted that exact role during the tests.
  • Security suspension and operational inactivity are represented independently.
  • PRAGMA foreign_key_check returns no rows.

19. Tenant Support Access Lease remains out of scope for 30I.1

Bite 30I.1 must not quietly implement the 30I.2 support-access mechanism.

  • Application Administrator still has no ordinary Tenant workspace access.
  • There is no Tenant Support Access Lease creation/approval/expiry UI in this Bite.
  • There is no manual way to turn the global Application Administrator Actor into a standing Tenant Actor.
  • Tenant business access remains denied unless/until Bite 30I.2 explicitly introduces the lease model.

PASS

30I.1 ends at the global control-plane + corrected lifecycle boundary; support leases remain deferred.


Promotion checklist

Before merging Bite 30I.1 into Bite 30I, confirm:

  • Test 1 — Global Application Administrator context: PASS
  • Test 2 — Exact control-plane permission allowlist: PASS
  • Test 3 — Tenant catalog administration without workspace authority: PASS
  • Test 4 — Audit Viewer does not expose Tenant data-plane shortcuts: PASS
  • Test 5 — Forced Tenant business-data access denied: PASS
  • Test 6 — Tenant Administrator scope isolation: PASS
  • Test 7 — Cross-Tenant Authentication Account reuse: PASS
  • Test 8 — Returning Person baseline verified: PASS
  • Test 9 — Global operational deactivation cascade: PASS
  • Test 10 — Tenant A isolated reactivation: PASS
  • Test 11 — Explicit privilege re-grant only: PASS
  • Test 12 — Later Tenant B reactivation isolation: PASS
  • Test 13 — New-Tenant onboarding uses reactivation transaction: PASS
  • Test 14 — Tenant Administrator cannot override security suspension: PASS
  • Test 15 — Application Administrator security-review boundary: PASS
  • Test 16 — Previous delegated privilege remains suspended: PASS
  • Test 17 — Authentication Administration lifecycle semantics: PASS
  • Test 18 — Final SQL invariant sweep: PASS
  • Test 19 — Support Access Lease remains deferred: PASS
  • No unexpected browser-console errors
  • No unexpected HTTP 5xx responses
  • No database foreign-key violations
  • Screenshots/evidence saved for any abnormal result

Promotion outcome

Bite 30I.1 manual promotion result: PASS / FAIL
Tester:
Date:
Branch: 754-bite-30i1-global-administration-control-plane
SHA: b773bb248fba779e59b8d525f09d0c426e2856b0
Notes: