Imported from vanyastaff/nebula (
crates/tenancy/AGENTS.md). Install upstream withnpx skills add vanyastaff/nebula --skill tenancy. Copyright stays with the author.
nebula-tenancy — Agent orientation
Agent quick-map for
crates/tenancy/. Full design:README.md. Repo-wide rules: rootAGENTS.md.
Purpose: Multi-tenancy security boundary — resolves an authenticated Principal into the port Scope and wraps the enumerated general Scope-taking storage ports in scope-substituting decorators so callers cannot forge another tenant's scope. Credential persistence is the deliberate owner-bound exception.
Layer: Business — depends only downward (nebula-storage-port, nebula-core; no credential, sqlx, adapter, or upward dependencies).
Commands
cargo nextest run -p nebula-tenancy· doctests: disabled ([lib] doctest = false— none to run)
Key files
src/lib.rs— re-export surface for the general principal-to-scope policy and scoped port decoratorssrc/resolver.rs—Principal,ScopeResolvertrait, defaultBindingScopeResolver,request_scope(&TenantContext); the fail-closedPrincipal→Scopeprojectionsrc/error.rs—TenancyError(MissingWorkspace/Unauthorized); coarse on purpose, never reveals which half mismatchedsrc/decorator/mod.rs+decorator/*.rs—Scoped*Storewrappers for execution, workflow, control_queue, idempotency, journal, node_result, resource, trigger, and webhook; each substitutes the bound scope on every call. This is not a promise that every storage-port trait has a decorator.
Conventions & never-do
- Substitute, never compare-and-reject. Decorators inject the bound
Scope; let the backendWHERE workspace_id=? AND org_id=?filter. A distinct "wrong scope" vs "no row" path is an existence oracle — id↔scope mismatch must surface asNotFound/Ok(None). - Fail-closed projection. Absent workspace binding ⇒
TenancyError::MissingWorkspace; never silently widen to org-only. Credential command authority is a separate injected policy owned bynebula-credentialand composed inapps/server; tenancy has no credential admin bypass. - This crate owns scoping policy only — it must NOT own the
Scopetype (that is Core-tiernebula-storage-portplain data) and must NOT add a backend/sqlx dependency. - Credential persistence is already owner-bound by
CredentialSelector; do not reintroduce a metadata-keyed credential decorator here. Audit/encryption/cache/backend composition remains innebula-storage.
See also
README.md— full design + threat-model table (spec §6.1) · ADR-0072 (storage port/adapter/tenancy split) · ADR-0029 (credential scope-layer fail-closed audit)