Skip to main content

Agent Skills Framework Extension

Multi-Tenant Architecture Skill

When to Use This Skill

Use this skill when implementing multi tenant architecture patterns in your codebase.

How to Use This Skill

  1. Review the patterns and examples below
  2. Apply the relevant patterns to your implementation
  3. Follow the best practices outlined in this skill

Production multi-tenancy patterns with isolation, security, and resource management.

Core Capabilities

  1. Isolation Strategies - Database per tenant, schema per tenant, row-level security
  2. Data Partitioning - Logical and physical separation
  3. Security Boundaries - Cross-tenant access prevention
  4. Resource Quotas - Compute, storage, API rate limits
  5. Tenant Lifecycle - Provisioning, migration, decommissioning

Row-Level Security (PostgreSQL)

-- migrations/multi_tenant_rls.sql
BEGIN;

-- Enable RLS on tables
ALTER TABLE projects ENABLE ROW LEVEL SECURITY;
ALTER TABLE tasks ENABLE ROW LEVEL SECURITY;
ALTER TABLE documents ENABLE ROW LEVEL SECURITY;

-- Policy: Users can only see data from their organization
CREATE POLICY tenant_isolation_policy ON projects
USING (organization_id = current_setting('app.current_tenant_id')::uuid);

CREATE POLICY tenant_isolation_policy ON tasks
USING (
project_id IN (
SELECT id FROM projects
WHERE organization_id = current_setting('app.current_tenant_id')::uuid
)
);

-- Policy: Admins bypass RLS
CREATE POLICY admin_access_policy ON projects
USING (current_setting('app.is_admin')::boolean = true);

COMMIT;

Tenant Context Middleware

// src/middleware/tenant_context.rs
use actix_web::{
dev::{Service, ServiceRequest, ServiceResponse, Transform},
Error, HttpMessage,
};
use futures::future::{ok, Ready};
use std::task::{Context, Poll};
use uuid::Uuid;

pub struct TenantContext;

impl<S, B> Transform<S, ServiceRequest> for TenantContext
where
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
S::Future: 'static,
{
type Response = ServiceResponse<B>;
type Error = Error;
type Transform = TenantContextMiddleware<S>;
type InitError = ();
type Future = Ready<Result<Self::Transform, Self::InitError>>;

fn new_transform(&self, service: S) -> Self::Future {
ok(TenantContextMiddleware { service })
}
}

pub struct TenantContextMiddleware<S> {
service: S,
}

impl<S, B> Service<ServiceRequest> for TenantContextMiddleware<S>
where
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
S::Future: 'static,
{
type Response = ServiceResponse<B>;
type Error = Error;
type Future = S::Future;

fn poll_ready(&self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.service.poll_ready(cx)
}

fn call(&self, req: ServiceRequest) -> Self::Future {
// Extract tenant ID from JWT claims
if let Some(tenant_id) = extract_tenant_id(&req) {
req.extensions_mut().insert(TenantId(tenant_id));
}

self.service.call(req)
}
}

#[derive(Clone, Debug)]
pub struct TenantId(pub Uuid);

fn extract_tenant_id(req: &ServiceRequest) -> Option<Uuid> {
// Extract from JWT claims
req.headers()
.get("Authorization")?
.to_str()
.ok()?
.strip_prefix("Bearer ")?
.parse()
.ok()
}

Tenant Provisioning

// src/tenants/provisioning.rs
use sqlx::PgPool;
use uuid::Uuid;

pub struct TenantProvisioner {
pool: PgPool,
}

impl TenantProvisioner {
pub async fn provision_tenant(
&self,
tenant_name: &str,
plan: &str
) -> Result<Uuid, sqlx::Error> {
let tenant_id = Uuid::new_v4();

let mut tx = self.pool.begin().await?;

// Create organization
sqlx::query!(
r#"
INSERT INTO organizations (id, name, plan, created_at)
VALUES ($1, $2, $3, NOW())
"#,
tenant_id,
tenant_name,
plan
)
.execute(&mut *tx)
.await?;

// Set resource quotas
let (max_users, max_projects, storage_gb) = match plan {
"free" => (5, 3, 10),
"pro" => (50, 50, 100),
"enterprise" => (500, 500, 1000),
_ => (5, 3, 10),
};

sqlx::query!(
r#"
INSERT INTO tenant_quotas (tenant_id, max_users, max_projects, storage_gb)
VALUES ($1, $2, $3, $4)
"#,
tenant_id,
max_users as i32,
max_projects as i32,
storage_gb as i32
)
.execute(&mut *tx)
.await?;

tx.commit().await?;

Ok(tenant_id)
}

pub async fn check_quota(
&self,
tenant_id: Uuid,
resource: &str
) -> Result<bool, sqlx::Error> {
let quota = sqlx::query!(
r#"
SELECT max_users, max_projects
FROM tenant_quotas
WHERE tenant_id = $1
"#,
tenant_id
)
.fetch_one(&self.pool)
.await?;

let current = sqlx::query!(
r#"
SELECT
(SELECT COUNT(*) FROM organization_members WHERE organization_id = $1) as users,
(SELECT COUNT(*) FROM projects WHERE organization_id = $1) as projects
"#,
tenant_id
)
.fetch_one(&self.pool)
.await?;

match resource {
"users" => Ok(current.users.unwrap_or(0) < quota.max_users.into()),
"projects" => Ok(current.projects.unwrap_or(0) < quota.max_projects.into()),
_ => Ok(false),
}
}
}

Kubernetes Namespace Isolation

# k8s/tenant-namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
name: tenant-acme-corp
labels:
tenant: acme-corp
---
apiVersion: v1
kind: ResourceQuota
metadata:
name: tenant-quota
namespace: tenant-acme-corp
spec:
hard:
requests.cpu: "10"
requests.memory: 20Gi
pods: "50"
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: deny-cross-tenant
namespace: tenant-acme-corp
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
ingress:
- from:
- namespaceSelector:
matchLabels:
tenant: acme-corp
egress:
- to:
- namespaceSelector:
matchLabels:
tenant: acme-corp

Usage Examples

RLS Setup

Apply multi-tenant-architecture skill to implement row-level security for tenant isolation

Tenant Provisioning

Apply multi-tenant-architecture skill to automate tenant provisioning with quota management

Namespace Isolation

Apply multi-tenant-architecture skill to configure Kubernetes namespace per tenant with network policies

Integration Points

  • database-design-patterns - RLS and partitioning
  • cloud-infrastructure-patterns - Resource isolation
  • k8s-statefulset-patterns - Per-tenant deployments

Success Output

When this skill completes successfully, output:

✅ SKILL COMPLETE: multi-tenant-architecture

Completed:
- [x] Row-level security (RLS) enabled on all tenant tables
- [x] Tenant context middleware implemented
- [x] Tenant provisioning with quota management operational
- [x] Kubernetes namespace isolation configured
- [x] Network policies applied for cross-tenant protection
- [x] Resource quotas enforced per tenant

Outputs:
- migrations/multi_tenant_rls.sql (RLS policies)
- src/middleware/tenant_context.rs (Context middleware)
- src/tenants/provisioning.rs (Provisioning service)
- k8s/tenant-namespace.yaml (K8s isolation)

Validation:
- [x] Cross-tenant access blocked (security test passed)
- [x] Quota enforcement verified (resource limits active)
- [x] Namespace isolation confirmed (network policy test passed)

Completion Checklist

Before marking this skill as complete, verify:

  • RLS policies created and enabled on all tenant-scoped tables
  • Tenant context middleware extracting tenant_id from JWT
  • Provisioning service creates tenants with appropriate quotas
  • Quota checking prevents resource limit violations
  • Kubernetes namespaces created per tenant with labels
  • NetworkPolicy blocks cross-tenant communication
  • ResourceQuota limits enforced (CPU, memory, pods)
  • Security test confirms no cross-tenant data leakage
  • Migration rollback tested and verified

Failure Indicators

This skill has FAILED if:

  • ❌ RLS policies not applied (cross-tenant data visible)
  • ❌ Tenant context not set (queries fail or return all data)
  • ❌ Provisioning creates tenant without quotas (unlimited resources)
  • ❌ Quota checks not enforced (tenants exceed limits)
  • ❌ Kubernetes namespaces missing labels (isolation incomplete)
  • ❌ NetworkPolicy allows cross-tenant traffic (security breach)
  • ❌ ResourceQuota not enforced (resource exhaustion possible)
  • ❌ Security test shows data leakage between tenants

When NOT to Use

Do NOT use this skill when:

  • Single-tenant application - Adds unnecessary complexity for one organization
  • Prototype/MVP - Multi-tenancy overhead slows initial development
  • Small user base - < 10 organizations don't justify isolation infrastructure
  • Simple resource sharing OK - No compliance requirements for data separation
  • No SaaS model - Self-hosted deployments per customer work better
  • Performance critical - RLS and isolation add query overhead
  • Legacy migration - Retrofitting multi-tenancy is complex; plan from start

Use these alternatives instead:

  • Single tenant: Standard database schema without RLS
  • Prototype: Defer multi-tenancy to post-MVP
  • Small scale: Separate databases per customer
  • No compliance needs: Shared tables with organization_id filtering
  • Self-hosted: Deploy separate instances per customer
  • Performance needs: Optimize single-tenant first, then add isolation

Anti-Patterns (Avoid)

Anti-PatternProblemSolution
No RLS on sensitive tablesData leakage, security breachEnable RLS on ALL tenant-scoped tables
Hardcoded tenant_idContext not dynamic, breaks isolationUse current_setting() for RLS policies
Missing quota checksResource exhaustion, unfair usageCheck quotas before creation operations
Shared Kubernetes namespaceNo isolation, resource conflictsCreate namespace per tenant with labels
No NetworkPolicyCross-tenant network access possibleApply deny-by-default NetworkPolicy
Unlimited resource quotasTenants consume all cluster resourcesSet CPU, memory, pod limits per plan tier
Skipping migration rollbackCan't recover from failed deploymentAlways test DOWN migrations
Admin bypass not implementedSupport can't troubleshoot tenant issuesAdd admin access policy with flag
Tenant ID in URL pathEnumeration attacks, security riskUse JWT claims, not URL parameters
No provisioning audit trailCan't track tenant lifecycleLog all provisioning/deprovisioning events

Principles

This skill embodies these CODITECT principles:

#2 First Principles Thinking

  • Understand WHY multi-tenancy matters (cost efficiency, SaaS scalability, compliance)
  • Question isolation needs: database, compute, network levels
  • Design from tenant needs up, not database capabilities down

#3 Keep It Simple

  • Start with row-level security (simplest isolation)
  • Add schema-per-tenant or database-per-tenant only if needed
  • Kubernetes namespaces for logical separation, not complex service meshes

#4 Separation of Concerns

  • Tenant context extraction separate from business logic
  • Provisioning service owns tenant lifecycle, not application code
  • RLS policies enforce isolation at database level, not application

#5 Eliminate Ambiguity

  • Explicit tenant_id required for all tenant-scoped operations
  • Clear quota limits per plan tier (free/pro/enterprise)
  • Unambiguous network policies (default deny, explicit allow)

#7 Security First

  • RLS prevents data leakage at database level
  • NetworkPolicy blocks unauthorized cross-tenant communication
  • Audit logging tracks all tenant provisioning/access

#8 No Assumptions

  • Don't assume RLS enabled; verify with security tests
  • Don't assume quotas enforced; test resource limit violations
  • Don't assume namespaces isolated; validate NetworkPolicy

#11 Automate Everything

  • Automated tenant provisioning with quota assignment
  • Automated namespace creation with labels and policies
  • Automated security testing for cross-tenant isolation

Full Standard: CODITECT-STANDARD-AUTOMATION.md


Version: 1.1.0 | Updated: 2026-01-04 | Quality Standard: SKILL-QUALITY-STANDARD.md v1.0.0