# Enforce Organization status at the shared authorization seam Type: task Status: resolved ## Question Make non-ACTIVE Organizations fail closed for project authorization, onboarding, queued execution, and Feishu triggers through one shared seam, with negative tests proving a SUSPENDED or ARCHIVED tenant cannot create, bind, trigger, resume, or mutate work while operator recovery remains explicit. ## Answer Organization lifecycle is now enforced at both authorization and mutation linearization points: - Project and project-group authorization resolves the owning Organization and denies every customer action before principal resolution when status is not `ACTIVE`. The legacy role helper follows the same rule. - Every customer mutation service (project/folder/binding/policy, explorer, members, teams, team grants, external-directory sync and slash-session mutation) verifies `ACTIVE` while holding a PostgreSQL `FOR SHARE` lock on the Organization row. A concurrent status `UPDATE` therefore orders before the operation (which fails closed) or after its transaction commits. - Agent session/run/project-lock admission is one transaction behind that same lifecycle lock. Queued triggers reauthorize when drained. Slash-command races return an explicit user-facing rejection instead of becoming deduplicated, silent failures. - Feishu attachments download into a private `HUB_PROJECT_WORKSPACE_ROOT/.cph-staging` batch outside every project. A durably committed ACTIVE run/project-lock admission is the linearization point; publication then uses exclusive same-filesystem hard links rather than copying bytes under a database lock. Failure compensates inode-tracked links, fails the admitted run and releases its lock. Startup removes abandoned staging batches and refuses staging symlinks. - Project creation holds the lifecycle lock across workspace allocation and DB creation. A later transaction failure removes the new leaf workspace; cleanup failure is surfaced together with the original error as `AggregateError`. - External-directory sync uses short lifecycle-serialized transactions per unit rather than holding the Organization row lock across an unbounded sync. No customer API can reactivate or otherwise change Organization lifecycle state. Operator recovery remains a separate platform-control-plane task. Evidence includes deterministic status/update lock ordering, direct-service negative tests for both `SUSPENDED` and `ARCHIVED`, queued-trigger and post-authorization races, DB-triggered workspace compensation (including cleanup failure), full local Hub tests, and the real Linux attachment trigger path.