fix: enforce active organization boundary

This commit is contained in:
2026-07-10 18:42:12 +08:00
parent f07f280b8f
commit d730e51c3d
24 changed files with 1686 additions and 460 deletions
@@ -1,7 +1,7 @@
# Enforce Organization status at the shared authorization seam
Type: task
Status: open
Status: resolved
## Question
@@ -9,3 +9,42 @@ 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.
@@ -47,6 +47,7 @@ rollback and recovery, and no known critical security or data-integrity gaps.
- [Provision a runnable non-root Hub service](issues/10-provision-nonroot-service.md) — install a strictly validated service identity and persistent layout, reject release/workspace overlap, validate complete production configuration and real bind settings, and execute Hub/Prisma/cph/bubblewrap probes as the service user under `no_new_privs` before writing the unit.
- [Confine the agent runtime and protect service credentials](issues/16-confine-agent-and-protect-credentials.md) — replace the Agent environment, deny host reads/writes from root, keep every writable SDK path inside the canonical project, require bubblewrap plus socat, emit bounded correlated diagnostics, and prove the boundary with the real Linux SDK under a capability-free non-root identity.
- [Close MCP context and file-delivery escape paths](issues/17-close-mcp-data-egress-escapes.md) — use no-follow project-rooted file operations and delivery snapshots, remove Git-root delivery, and bind every Feishu MCP read to the run's project and chat.
- [Enforce Organization status at the shared authorization seam](issues/22-enforce-organization-status.md) — deny non-ACTIVE tenants before principal resolution, serialize every customer mutation and run admission against Organization lifecycle updates, reauthorize queued work, and stage/publish attachments with crash recovery and explicit filesystem compensation.
## Fog