Tenant-Project Ownership Baseline (MVP)¶
Purpose¶
Define the canonical ownership model for GPUaaS before further feature coding. This baseline is mandatory for all new implementation work.
Scope¶
- Tenant/project hierarchy and ownership semantics.
- Auth context and access control boundaries.
- Billing ownership model.
- Contract and schema impact for MVP reset-baseline.
- Minimal IAM shape to avoid future query rewrites.
Canonical Hierarchy¶
Tenant (org)
-> Project
-> Resources (allocations, usage records, ledger entries, payment sessions, storage objects, scheduler resources)
Identity hierarchy remains separate:
User (human actor) / Service identity
-> Membership-based role assignment (tenant/project)
-> Request context includes active project
Membership Model (MVP-Compatible, Future-Proof)¶
Introduce membership tables now and enforce single-tenant behavior by constraint:
1. tenant_memberships(tenant_id, user_id, role, ...) with UNIQUE(user_id) in MVP.
2. project_memberships(project_id, user_id, role, ...) with UNIQUE(project_id, user_id).
3. All authz/resource-access queries must resolve through memberships (not users.org_id direct ownership checks).
4. When multi-tenant users are needed later, drop UNIQUE(user_id) on tenant_memberships without rewriting authz query shape.
Mandatory Ownership Rules¶
- Tenant is the legal/billing ownership root.
- Project is the operational resource-ownership scope.
- User is an actor and attribution identity, not owner-of-record.
- Every allocation must belong to exactly one tenant and one project.
- Access checks for resources must be tenant/project scoped;
requested_by_user_idis audit only.
Signup / Bootstrap Model¶
MVP uses one automatic path for every new user:
1. Create personal tenant (organizations.type = personal).
2. Create default project under that tenant (slug = default).
3. Bind user to that tenant context.
4. Create membership rows:
- tenant: owner
- project: owner
5. Issue auth context with tenant claim; project is selected per request context.
No floating users without tenant/project context are allowed.
Auth Context Model¶
- JWT/session must include tenant context (
org_idclaim in current schema). - Active project is request-scoped (header/body/path), validated server-side.
- Tenant context from JWT and request context must both pass membership validation.
- Header-only tenant context (
X-Tenant-IDwithout token tenant claim) is deferred to a future IAM phase. - User-level policy overrides are out of MVP scope.
- Missing project context on project-scoped endpoints is rejected (
400 invalid_request); there is no implicit server-side default project fallback.
Role Model (Two-Tier, Explicit)¶
- Platform role (
users.role) is global control-plane authority: admin: platform superadminuser: non-platform-admin- Tenant/project roles live in membership tables and are tenant-scoped authorization roles:
- tenant:
owner,admin,member,billing_viewer - project:
owner,admin,member,viewer - Tenant/project access checks must not rely on
users.roleexcept for explicit platform-admin override paths.
Billing Model¶
- Billing balance and Stripe customer identity belong to tenant.
- Ledger entries are tenant-owned financial records.
- User fields in billing/payment tables are actor attribution (
initiated_by_user_id,requested_by_user_id). - Personal tenants behave like single-user billing without special-case code paths.
- Project-level billing customer override is a deferred option (
projects.stripe_customer_idnullable, fallback to tenant customer).
MVP IAM Scope (Intentionally Simple)¶
- Keep role model small for MVP (
owner,admin,member,billing_viewer). - Keep single-tenant-per-user behavior via
UNIQUE(user_id)intenant_memberships. - Defer tenant-level groups and project-level groups to post-MVP IAM phase.
Reset-Baseline Implementation Policy¶
This repo is pre-production and can reset state. 1. No data migration/backfill strategy is required for this change. 2. Recreate schema and seed data to enforce ownership constraints from day one. 3. Remove transitional nullable ownership behavior in code.
Required Schema Constraints (MVP Baseline)¶
At minimum, enforce:
1. allocations.org_id NOT NULL.
2. allocations.project_id NOT NULL.
3. allocations.project_id references projects.id where projects.org_id = allocations.org_id (mandatory database-level composite constraint).
4. Membership tables exist and are used in authorization paths:
- tenant_memberships with UNIQUE(user_id) (MVP constraint)
- project_memberships with UNIQUE(project_id, user_id)
5. Any new tenant-scoped mutable resource must carry non-null tenant/project ownership or explicitly document why project scope is not applicable.
6. nodes.org_id nullable semantics must be explicit:
- null means shared/unassigned pool node not dedicated to a single tenant.
- non-null means tenant-dedicated node.
Contract Impact (Planned)¶
- Public API endpoints that mutate project-owned resources require explicit project context.
- Resource list/read endpoints must filter by tenant + project authorization context.
- Billing/payment endpoints must expose tenant-owned balance semantics.
- Canonical
resource_nameshould be emitted on tenant/project-scoped resources using the Resource Identifier Spec. - Error semantics remain unchanged (
ownership_required,insufficient_permissions, etc.) but checks move to tenant/project boundaries.
Policy Scope Impact (Planned)¶
- Current per-user concurrency policy should be replaced with tenant/project scope keys.
- Policy resolution chain for MVP remains:
- global defaults
- tenant override
- project override
- Concurrency policy should support both project cap and tenant cap keys.
- Conflict handling is deterministic: most-specific scope wins (project overrides tenant; tenant overrides global).
policy_values.scope_type='plan'is retained for Phase-2 pricing/subscription layering and is out of MVP evaluation chain.policy_values.scope_type='user'may remain in schema for forward compatibility but is out of MVP evaluation chain.- Planned Phase-2 chain:
global -> plan -> tenant -> project(most-specific wins).
Idempotency TTL Baseline¶
idempotency_keys.expires_atuses a 24h baseline TTL in MVP.- A policy key
idempotency.key_ttl_secondsis planned for explicit runtime configurability.
OPA-Ready Design Constraint (No Rewrite Later)¶
- Authorization decisions should be funneled through a single decision interface in API/service code.
- MVP may continue in-code evaluation, but decision input/output shape must be policy-engine friendly:
- input: actor, tenant, project, action, resource, attributes
- output: allow/deny, reason, policy source
- Full OPA/OPAL rollout is deferred; embedded OPA pilot is allowed later without changing endpoint contracts.
Authorization Performance SLO (MVP)¶
- Membership-resolution SLO target (API-side authorization checks):
- p95 <= 20 ms
- p99 <= 50 ms
- Scope:
- tenant + project membership resolution path used by project-scoped endpoints.
- Enforcement posture:
- query/index optimization first, no async permission cache by default.
- cache design is triggered only if sustained p95/p99 breaches remain after query/index tuning.
- Required evidence in acceptance:
- explain-plan proof that membership resolution uses indexed paths.
- integration test coverage for active-membership-only semantics (
deleted_at is null).
Implementation Risks & Triggers¶
- Membership resolution latency risk:
- Risk: high-frequency authorization checks can regress if membership/policy lookups become join-heavy.
- MVP posture: optimize query/index paths first; do not add asynchronous authorization cache by default.
- Trigger for cache design: sustained p95 authz-check latency or API p95 breach under representative load after index/query tuning.
- Role-rigidity risk:
- Risk: hardcoded role-name checks (
if role == ...) make ABAC/OPA migration expensive. - Required implementation shape: map actions to permissions and evaluate by permission set, not literal role branching in handlers.
- Multi-tenant user expansion risk:
- Risk: forgetting that MVP single-tenant behavior is enforced by
UNIQUE(user_id)intenant_memberships. - Constraint contract: authz query shape must remain membership-based so multi-tenant enablement is a constraint change, not a query rewrite.
- Audit continuity risk:
- Risk: hard-deleting memberships erases operational evidence for historical authorization attribution.
- MVP posture: memberships are soft-deleted; authz queries only consider active rows (
deleted_at is null).
Non-Goals (MVP)¶
- Multi-tenant user context switching UX.
- Project-level role matrix.
- IAM group management UI.
- Full service-account runtime implementation (model/spec is required pre-work).
Acceptance Gate Before Coding¶
Ownership baseline is considered ready when: - ADR accepted and linked in roadmap. - ERD and db schema updated to non-null ownership semantics. - OpenAPI contract reflects project context requirements. - Test standards include tenant/project authorization checks for resource endpoints.
References¶
doc/architecture/adrs/ADR-008-tenant-project-ownership-baseline.mddoc/architecture/ERD.mddoc/architecture/db_schema_v1.sqldoc/architecture/Schema_Migration_Plan.mddoc/Implementation_Roadmap.mddoc/architecture/Resource_Identifier_Spec.md