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
ACTIVEwhile holding a PostgreSQLFOR SHARElock on the Organization row. A concurrent statusUPDATEtherefore 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-stagingbatch 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.