The disabled-org gate added in #1860 covers the management and resource RPCs (the authorization map), but the SpiceDB Check API is deliberately out of its scope:
CheckResourcePermission
BatchCheckPermission
CheckFederatedResourcePermission
These are on the authorization skip list and answer purely from SpiceDB tuples. Because disabling an org leaves its tuples in place (by design, so re-enable restores access), these endpoints keep returning allowed = true for resources of a disabled org. External services that gate their own access on these checks will keep serving a disabled org's resources.
Two ways to close it
- Handler/service guard (Postgres read): resolve each checked resource to its org and return
false when the org is disabled. Simple, no migration, but adds 1–3 point reads per check on a hot path, and overloads "denied" to also mean "suspended".
- SpiceDB marker relation (preferred): add a
suspended relation on the org that every permission subtracts (- org->suspended); write the tuple on disable, remove it on enable. Reflects org state in every check — single, batch, and federated — with no extra Postgres reads, since it rides the existing permission traversal. Requires a schema migration, disable/enable flow changes, and a backfill for currently-disabled orgs. Care needed so platform/superuser permissions are not subtracted (admins must still manage and re-enable).
Recommendation: option 2.
Relates to #1585.
The disabled-org gate added in #1860 covers the management and resource RPCs (the authorization map), but the SpiceDB Check API is deliberately out of its scope:
CheckResourcePermissionBatchCheckPermissionCheckFederatedResourcePermissionThese are on the authorization skip list and answer purely from SpiceDB tuples. Because disabling an org leaves its tuples in place (by design, so re-enable restores access), these endpoints keep returning
allowed = truefor resources of a disabled org. External services that gate their own access on these checks will keep serving a disabled org's resources.Two ways to close it
falsewhen the org is disabled. Simple, no migration, but adds 1–3 point reads per check on a hot path, and overloads "denied" to also mean "suspended".suspendedrelation on the org that every permission subtracts (- org->suspended); write the tuple on disable, remove it on enable. Reflects org state in every check — single, batch, and federated — with no extra Postgres reads, since it rides the existing permission traversal. Requires a schema migration, disable/enable flow changes, and a backfill for currently-disabled orgs. Care needed so platform/superuser permissions are not subtracted (admins must still manage and re-enable).Recommendation: option 2.
Relates to #1585.