Files
suaveplan/openspec/changes/enterprise-api-plugin-tenant/proposal.md
2026-05-27 23:29:53 +01:00

3.0 KiB

OpenSpec Change: enterprise-api-plugin-tenant

  • Change ID: enterprise-api-plugin-tenant
  • Package: @suaveplan/api-plugin-tenant
  • Category: api/auth
  • Target path: api/auth/api-plugin-tenant
  • Status: PROPOSED
  • Date: 2026-04-24

Summary

Deliver @suaveplan/api-plugin-tenant on the genesis branch from scratch at enterprise quality. The plugin provides multi-tenant management: tenant provisioning (create/update/suspend/delete), member invitation and role management, per-tenant feature flag overrides via @suaveplan/feature-flags, per-tenant settings/branding, and a tenant-scoped audit trail. Routes mounted under /tenants. Events: tenant:created, tenant:suspended, tenant:deleted, tenant:member:invited, tenant:member:removed.


Available workspace deps on genesis

Package Role
@suaveplan/api (workspace:^) definePlugin
@suaveplan/api-shared (workspace:^) Helpers
@suaveplan/api-plugin-auth (workspace:^) requireAuth, requireRole
@suaveplan/api-plugin-database (workspace:^) SurrealDB
@suaveplan/feature-flags (workspace:^) Per-tenant flag overrides
@suaveplan/audit-logging (workspace:^) Tenant audit trail
@suaveplan/events (workspace:^) Event bus
@suaveplan/error (workspace:^) TenantError hierarchy
@suaveplan/rate-limiter (workspace:^) Rate limits
hono + @hono/zod-openapi + zod Routes/schemas
@opentelemetry/api OTel

Public API surface

function createTenantPlugin(config?: TenantPluginConfig): PluginDescriptor<TenantPluginConfig>

class TenantService {
  createTenant(data: CreateTenantInput): Promise<Tenant>
  getTenant(id: string): Promise<Tenant>
  updateTenant(id: string, data: UpdateTenantInput): Promise<Tenant>
  suspendTenant(id: string, reason: string): Promise<void>
  deleteTenant(id: string): Promise<void>
  inviteMember(tenantId: string, email: string, role: string): Promise<TenantInvite>
  acceptInvite(token: string): Promise<void>
  removeMember(tenantId: string, userId: string): Promise<void>
  listMembers(tenantId: string): Promise<TenantMember[]>
  getSettings(tenantId: string): Promise<TenantSettings>
  updateSettings(tenantId: string, settings: Partial<TenantSettings>): Promise<TenantSettings>
}

Error codes

type TenantErrorCode =
  | "TENANT_NOT_FOUND"
  | "TENANT_SUSPENDED"
  | "TENANT_INVITE_EXPIRED"
  | "TENANT_INVITE_INVALID"
  | "TENANT_MEMBER_NOT_FOUND"
  | "TENANT_MEMBER_ALREADY_EXISTS"

File layout

api/auth/api-plugin-tenant/
  package.json  tsconfig.json  vitest.config.ts
  surql/ (tenant, tenant_member, tenant_invite, tenant_settings tables)
  src/
    index.ts  events.ts
    types/  errors/
    services/tenant/
    routes/tenants/
  tests/integration.test.ts

Impact

Prerequisites: @suaveplan/api, @suaveplan/api-plugin-auth, @suaveplan/api-plugin-database, @suaveplan/feature-flags, @suaveplan/audit-logging.