Files

2.6 KiB

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.