ADR-0073: Organization Lifecycle and Cascading Suspension
Accepted — org suspension semantics, Redis revocation event_type, and last-owner invariant.
ADR-0073: Organization Lifecycle and Cascading Suspension
- Status: Accepted
- Date: 2026-09-09
- Authors: IBEX Harness team
- Milestone: 4.A.2 Org & User Management API
Context
The system stores tenant lifecycle on ibex_core.organizations.status with values such as active and suspended.
Suspension must become effective quickly (same overall SLA as auth revocation invalidation), and the proxy must
deny suspended-org access rather than letting tokens continue to work until TTL expiry.
IBEX already has a Redis pub/sub mechanism for token revocation propagation (ADR-0029). That channel is global and its primary invalidation primitive today is token-id scrubbing in the auth cache.
This ADR defines how to:
- Fail-closed on
ValidateTokenwhen the caller'sorg_idis suspended. - Propagate suspension to proxy cache instances quickly.
- Keep authorization semantics consistent by using both
users.role(org hierarchy) and the presented token's permission bitmap (ADR-0009). - Enforce the last-owner invariant safely at the API layer.
Decision
1) Suspension vs deletion semantics
- Suspension is reversible and must block proxy traffic quickly (fail-closed).
- Deletion is irreversible and may require an ordered cascade job (GDPR).
Only suspension semantics are covered in this ADR; deletion cascade mechanics belong in the 4.A.2 implementation ADR and follow-up issues.
2) Reuse the existing revocation channel with an event_type
- Channel:
ibex:token:revocations(ADR-0029) - Event shape: JSON
RevocationEventextended with anevent_typediscriminator:event_type = "token"for token revocations (existing behavior)event_type = "org_suspend"for org suspension propagation
Backward-compatibility rule:
- If
event_typeis missing, treat the event asevent_type="token". - Token-revocation events must continue to include
token_idand be valid under the previous schema. - Subscribers must treat
org_suspendevents as an alternative invalidation path that does not require a token_id → digest mapping.
3) Auth-side fail-closed check in ValidateToken
The AuthService.ValidateToken implementation must verify:
- The token row is active and not revoked/expired (existing).
- The token's
org_idbelongs to an organization withorganizations.status = 'active'.
If the organization is not active, return a stable domain error representing org suspension. No proto wire changes are required if this can live in auth server-side logic.
4) Proxy-side cache invalidation and HTTP mapping
On the proxy, the revocation subscriber must handle:
event_type="token": call existingCachingValidator.InvalidateByTokenID(token_id)event_type="org_suspend": callCachingValidator.InvalidateByOrgID(org_id)and scrub all relevant cached entries
HTTP error mapping:
- Denials due to suspended org must map to a dedicated canonical API error code:
ORG_SUSPENDED
- The mapping should use 403 (Forbidden) semantics to distinguish from invalid/expired tokens, ensuring suspended org denials do not poison the auth cache bloom filter intended for invalid-token caching.
5) Last-owner invariant enforcement (API-layer lock)
When demoting org roles (e.g. PATCH user role):
- Start a transaction under the org scope.
- Lock matching owner rows, then count them in application logic:
SELECT id FROM ibex_core.users WHERE org_id = $1 AND role = 'owner' AND deleted_at IS NULL FOR UPDATE- Deny when
len(rows) <= 1would leave the org without an owner.
- If the caller would demote the final owner, deny with:
409 LAST_OWNER_PROTECTED
This is enforced in the API layer because database-only constraints cannot express "last owner" safely and ergonomically for this workflow, and because the API layer needs a deterministic error code.
6) Role ↔ permission-bitmap mapping (ADR-0009)
The management API must use both:
users.rolefor org hierarchy invariants- the presented token's ADR-0009 permission bitmap to gate whether the PAT is allowed to call management endpoints
This ADR documents the role → bitmap mapping used for interpreting the management surface:
owner→permissions.Adminadmin→permissions.Adminmember→permissions.AgentDefaultviewer→permissions.ReadOnly
If the presented token bitmap is missing required bits for a given management operation, deny even if the user's role would allow it; conversely, do not rely on token bitmap bits alone for last-owner protection.
Consequences
- Suspended-org access becomes fail-closed quickly (cache invalidation + auth-side join/check).
- The revocation propagation mechanism remains single-channel (ADR-0029) by adding an
event_typediscriminator. - The management API authorization semantics are deterministic and auditable: role enforces hierarchy, bitmap enforces token capabilities.
Alternatives considered
- Separate Redis channel for org suspension
- Rejected: introduces second propagation mechanism with extra operational complexity.
- Only invalidate cache entries without auth-side checks
- Rejected: brand-new/uncached tokens issued after suspension could still work until natural expiry.
- Add org suspension status to proto
- Rejected: avoid unnecessary wire expansion if server-side logic is sufficient.
References
- ADR-0029 Token revocation propagation via Redis pub/sub
- ADR-0009 Permission bitmap layout
- 4.A.2 Org & User Management API
Was this page helpful?
Last updated on