Tenant and User Access Foundation - RodrigoMattosoSilveira/EnterpriseRemoteSystems GitHub Wiki

Manual promotion checklist

We will use targeted curl requests where noted.

Test setup

Use disposable records:

  • Tenant code: B28A_MANUAL_20260719
  • Tenant name: Bite 28A Manual Test
  • Actor key: bite28a-manual-admin
  • Begin as:
  • Actor: bootstrap-admin
  • Tenant: default

The test tenant cannot be deleted by design. Leave it inactive after testing so it remains an intentional audit artifact.

1. Navigation and tenant catalog

  • Open /people.
    • Click Tenants.
    • Confirm /admin/tenants loads without an API error.
    • Confirm the existing Default Tenant appears.
    • Confirm the table shows:
    • Tenant name, code, and ID.
    • Operational status.
    • Tenant Admin count.
    • Manage action.
    • Confirm links to Authorization, Reference Data, and People work.

Expected: The catalog is usable as bootstrap-admin, and existing tenant data is unchanged.

2. Create a tenant

Create the disposable tenant using lowercase input:

  • Code: b28a_manual_20260719
  • Name: Bite 28A Manual Test
  • Description: Manual promotion test for Bite 28A
  • Activate immediately: checked

Confirm:

  • A success message appears.
  • The code is stored and displayed as B28A_MANUAL_20260719.
  • The tenant appears only once in the catalog.
  • Status is Active · Needs admin.
  • Tenant Admin count is 0.
  • Refreshing the browser preserves the tenant.
  • Duplicate-code validation

Attempt to create another tenant using:

  • Code: b28a_manual_20260719
  • A different name

Expected:

  • Creation is rejected.
  • The error identifies the code field.
  • No duplicate tenant appears.
  • The comparison is case-insensitive.

3. Edit tenant identity

Open Manage for the disposable tenant.

Change:

  • Name to Bite 28A Updated Tenant
  • Description to Updated during manual promotion testing
  • Optionally change the code to another valid unique uppercase value and then restore it.

Click Save Tenant.

Confirm:

  • A success message appears.
  • The new values appear immediately.
  • The values remain correct after refresh.
  • The catalog reflects the updated name and description.
  • Blank name and invalid code formats are rejected without a server error.

4. Assign a Tenant Admin

In /admin/authorization:

  1. Open /admin/authorization.
  2. Confirm the operating actor is:
    1. Actor ID/key: bootstrap-admin
    2. Tenant ID: default
  3. Under Create actor, select a disposable collaborator.
  4. Observe the Derived actor identity panel. Record the displayed actor key.
  5. Click Create Actor.
  6. Confirm the new actor appears in the Actors list and is active.
  7. Do not grant TENANT_ADMIN from the Authorization page.
  8. Return to /admin/tenants/.
  9. Under Tenant administrators, select the newly created actor.
  10. Click Assign Admin.
  11. Confirm:
    1. The actor appears in the assigned administrators list.
    2. A persisted TENANT_ADMIN grant exists for the test tenant.
    3. The Tenant Admin count becomes 1.
    4. Operational status becomes Active · Ready.

For the later curl tests, use the derived actor key:

TENANT_ADMIN="collaborator-<selected-collaborator-uuid>"
TENANT_ID="<test-tenant-id>"

curl -i \
  -H "X-Actor-ID: ${TENANT_ADMIN}" \
  -H "X-Tenant-ID: ${TENANT_ID}" \
  "http://localhost:8080/api/v1/tenants/${TENANT_ID}"

Expected result: HTTP 200.

5. Tenant Admin authorization boundary

Copy the disposable tenant ID from its detail-page URL.

In Authorization Admin, select:

  • Actor: bite28a-manual-admin
  • Tenant ID: the disposable tenant ID

Use these commands, replacing the variables:

BASE_URL=http://localhost:8080/api/v1
TENANT_ID="<disposable-tenant-id>"
TENANT_ADMIN="bite28a-manual-admin"
Own-tenant read
curl -i \
  -H "X-Actor-ID: ${TENANT_ADMIN}" \
  -H "X-Tenant-ID: ${TENANT_ID}" \
  "${BASE_URL}/tenants/${TENANT_ID}"

Expected: HTTP 200 with the disposable tenant.

Full catalog access

curl -i \
  -H "X-Actor-ID: ${TENANT_ADMIN}" \
  -H "X-Tenant-ID: ${TENANT_ID}" \
  "${BASE_URL}/tenants"

Expected: HTTP 403.

Other-tenant access curl -i
-H "X-Actor-ID: ${TENANT_ADMIN}"
-H "X-Tenant-ID: ${TENANT_ID}"
"${BASE_URL}/tenants/default"

Expected: HTTP 403.

Tenant creation curl -i
-X POST
-H "Content-Type: application/json"
-H "X-Actor-ID: ${TENANT_ADMIN}"
-H "X-Tenant-ID: ${TENANT_ID}"
-d '{"code":"FORBIDDEN_TEST","name":"Forbidden Test"}'
"${BASE_URL}/tenants"

Expected: HTTP 403, and no tenant is created.

Also test the own-tenant browser view. The Tenant Admin must be able to understand its tenant’s identity and status without gaining access to tenant-catalog mutation. Treat misleading edit controls or an authorization error that prevents meaningful own-tenant viewing as a UX defect.

6. Deactivate the tenant

Switch back to:

  • Actor: bootstrap-admin
  • Tenant: default

Do so: Open:

/admin/authorization

In the Admin request actor section, set:

Actor ID: bootstrap-admin
Tenant ID: default

Then click the button that applies/saves the request actor context.

Afterward, refresh the page. The current request context should show:

Actor: bootstrap-admin Tenant: default

You can verify it from the terminal with:

curl -i \
  -H "X-Actor-ID: bootstrap-admin" \
  -H "X-Tenant-ID: default" \
  "http://localhost:8080/api/v1/authz/current-actor"

Expected: HTTP 200, with bootstrap-admin resolved as the current actor.

On the disposable tenant detail page, click Deactivate Tenant.

Confirm:

  1. A success message appears.
  2. Status becomes Inactive.
  3. The assigned Tenant Admin remains listed.
  4. Tenant history and identity remain readable.
  5. The tenant remains in the catalog.
  6. Refreshing does not reactivate it.

7. Inactive-tenant enforcement

Use the assigned Tenant Admin and inactive tenant ID.

Historical read remains available

curl -i \
  -H "X-Actor-ID: ${TENANT_ADMIN}" \
  -H "X-Tenant-ID: ${TENANT_ID}" \
  "${BASE_URL}/people"

Expected: HTTP 200. An empty result is acceptable.

Normal write is blocked

curl -i \
  -X POST \
  -H "Content-Type: application/json" \
  -H "X-Actor-ID: ${TENANT_ADMIN}" \
  -H "X-Tenant-ID: ${TENANT_ID}" \
  -d '{}' \
  "${BASE_URL}/people"

Expected:

HTTP 423.
Error code: tenant_inactive.
Message explains that historical records remain readable but operations are blocked.
The request must not proceed to normal Person validation.

Repeat one additional tenant-scoped write through the browser or API, such as an expense, collaborator, reference-data, or planning mutation.

Expected: The same 423 tenant_inactive behavior.

8. Application Admin recovery

While the disposable tenant is inactive:

  1. Operate as bootstrap-admin.
  2. Open its tenant detail page.
  3. Confirm tenant management remains available.
  4. Click Activate Tenant.

Confirm:

  • The tenant becomes active again.
  • Its Tenant Admin assignment remains intact.
  • Status returns to Active · Ready.
  • The same empty Person POST no longer returns 423 tenant_inactive; it should instead reach ordinary request validation.

This proves an Application Admin can recover an inactive tenant.

9. Revoke the Tenant Admin

  1. As bootstrap-admin, click Revoke beside the disposable Tenant Admin.

Confirm:

  • The grant disappears from the assigned list.
  • Tenant Admin count becomes 0.
  • The active tenant status changes to Active · Needs admin.
  • The actor itself remains active.
  • Unrelated grants on the actor are not changed.
  • The revoked actor can no longer read the tenant using the former Tenant Admin authority.
  • Reassigning the actor reactivates the same logical grant without creating duplicate administrator counts.

After confirming reassignment, revoke it again for cleanup.

10. Tenant deletion protection

There should be no delete action in the tenant UI.

Attempt API deletion as bootstrap-admin:

curl -i \
  -X DELETE \
  -H "X-Actor-ID: bootstrap-admin" \
  -H "X-Tenant-ID: default" \
  "${BASE_URL}/tenants/${TENANT_ID}"

Expected:

  • HTTP 409.
  • Error code: tenant_deletion_not_allowed.
  • The response instructs the administrator to deactivate the tenant.
  • The tenant remains readable and still appears in the catalog afterward.
    1. Audit log verification

Open /admin/audit-logs and locate the disposable tenant ID.

Confirm authorized audit records exist for the actions performed:

  • tenants.create
  • tenants.update
  • tenants.admins.assign
  • tenants.deactivate
  • tenants.activate
  • tenants.admins.revoke

For each entry, confirm:

  • Actor is bootstrap-admin.
  • Target type is tenant.
  • Target ID is the disposable tenant ID.
  • Decision is authorized.
  • HTTP method and path correspond to the operation.

12. Existing-tenant regression

Return to actor bootstrap-admin, tenant default, and verify:

  • People list loads.
  • Collaborators load.
  • Expenses load.
  • Work Period Planning loads.
  • Reference Data loads.
  • Authorization Admin loads.
  • At least one established, reversible write in the Default Tenant succeeds.

A practical reversible write is to create a disposable reference-data item and then deactivate it.

Expected: Existing active-tenant operations behave exactly as before Bite 28A and do not return tenant_inactive.