forked from EduCraft/curriculum-project-hub
fix: enforce active organization boundary
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user