# 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.
