feat(phase-3): workstream 1 — Multi-Tenancy
Introduces full multi-tenant organization model to AgentIdP: Schema: - 6 migrations: organizations + organization_members tables; organization_id FK added to agents, credentials, audit_logs; PostgreSQL RLS policies on all three tables; system org seed + backfill API: - 6 new /api/v1/organizations endpoints (CRUD + members) gated by admin:orgs scope - OPA scopes.json updated with 6 new org endpoint → admin:orgs mappings Implementation: - OrgRepository, OrgService, OrgController, createOrgsRouter - OrgContextMiddleware: sets app.organization_id session variable so RLS enforces per-request org isolation at the database layer - JWT payload extended with organization_id claim; auth.ts backfills org_system for backward-compatible tokens - New error classes: OrgNotFoundError, OrgHasActiveAgentsError, AlreadyMemberError Tests: 373 passing, 80.64% branch coverage, zero any types Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -28,7 +28,7 @@ export type DeploymentEnv = 'development' | 'staging' | 'production';
|
||||
export type CredentialStatus = 'active' | 'revoked';
|
||||
|
||||
/** OAuth 2.0 scope values supported by this IdP. */
|
||||
export type OAuthScope = 'agents:read' | 'agents:write' | 'tokens:read' | 'audit:read';
|
||||
export type OAuthScope = 'agents:read' | 'agents:write' | 'tokens:read' | 'audit:read' | 'admin:orgs';
|
||||
|
||||
/** Audit action identifiers for all significant platform events. */
|
||||
export type AuditAction =
|
||||
@@ -43,7 +43,11 @@ export type AuditAction =
|
||||
| 'credential.generated'
|
||||
| 'credential.rotated'
|
||||
| 'credential.revoked'
|
||||
| 'auth.failed';
|
||||
| 'auth.failed'
|
||||
| 'org.created'
|
||||
| 'org.updated'
|
||||
| 'org.deleted'
|
||||
| 'org.member_added';
|
||||
|
||||
/** Outcome of an audited action. */
|
||||
export type AuditOutcome = 'success' | 'failure';
|
||||
@@ -55,6 +59,7 @@ export type AuditOutcome = 'success' | 'failure';
|
||||
/** Full representation of a registered AI agent identity. */
|
||||
export interface IAgent {
|
||||
agentId: string;
|
||||
organizationId: string;
|
||||
email: string;
|
||||
agentType: AgentType;
|
||||
version: string;
|
||||
@@ -74,6 +79,7 @@ export interface ICreateAgentRequest {
|
||||
capabilities: string[];
|
||||
owner: string;
|
||||
deploymentEnv: DeploymentEnv;
|
||||
organizationId?: string;
|
||||
}
|
||||
|
||||
/** Request body for partially updating an agent. */
|
||||
@@ -172,6 +178,8 @@ export interface ITokenPayload {
|
||||
iat: number;
|
||||
/** Expiry (Unix seconds). */
|
||||
exp: number;
|
||||
/** Organization the agent belongs to (optional for backward compat). */
|
||||
organization_id?: string;
|
||||
}
|
||||
|
||||
/** OAuth 2.0 token request (form-encoded). */
|
||||
|
||||
90
src/types/organization.ts
Normal file
90
src/types/organization.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
/**
|
||||
* Organization and multi-tenancy types for SentryAgent.ai AgentIdP.
|
||||
* All interfaces and types for the organization/tenant domain live here.
|
||||
*/
|
||||
|
||||
// ============================================================================
|
||||
// Enumerations / Union Types
|
||||
// ============================================================================
|
||||
|
||||
/** Lifecycle status of an organization. */
|
||||
export type OrgStatus = 'active' | 'suspended' | 'deleted';
|
||||
|
||||
/** Subscription plan tier for an organization. */
|
||||
export type PlanTier = 'free' | 'pro' | 'enterprise';
|
||||
|
||||
/** Role of an agent within an organization. */
|
||||
export type OrgRole = 'member' | 'admin';
|
||||
|
||||
// ============================================================================
|
||||
// Organization
|
||||
// ============================================================================
|
||||
|
||||
/** Full representation of a registered organization (tenant). */
|
||||
export interface IOrganization {
|
||||
organizationId: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
planTier: PlanTier;
|
||||
maxAgents: number;
|
||||
maxTokensPerMonth: number;
|
||||
status: OrgStatus;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
/** Request body for creating a new organization. */
|
||||
export interface ICreateOrgRequest {
|
||||
name: string;
|
||||
slug: string;
|
||||
planTier?: PlanTier;
|
||||
maxAgents?: number;
|
||||
maxTokensPerMonth?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Request body for partially updating an organization.
|
||||
* Note: status may only be set to 'active' or 'suspended' via update;
|
||||
* 'deleted' is applied only through the soft-delete operation.
|
||||
*/
|
||||
export interface IUpdateOrgRequest {
|
||||
name?: string;
|
||||
planTier?: PlanTier;
|
||||
maxAgents?: number;
|
||||
maxTokensPerMonth?: number;
|
||||
status?: 'active' | 'suspended';
|
||||
}
|
||||
|
||||
/** Paginated list of organizations. */
|
||||
export interface IPaginatedOrgsResponse {
|
||||
data: IOrganization[];
|
||||
total: number;
|
||||
page: number;
|
||||
limit: number;
|
||||
}
|
||||
|
||||
/** Query filters for listing organizations. */
|
||||
export interface IOrgListFilters {
|
||||
status?: OrgStatus;
|
||||
page: number;
|
||||
limit: number;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Organization Membership
|
||||
// ============================================================================
|
||||
|
||||
/** An agent's membership record within an organization. */
|
||||
export interface IOrgMember {
|
||||
memberId: string;
|
||||
organizationId: string;
|
||||
agentId: string;
|
||||
role: OrgRole;
|
||||
joinedAt: Date;
|
||||
}
|
||||
|
||||
/** Request body for adding an agent to an organization. */
|
||||
export interface IAddMemberRequest {
|
||||
agentId: string;
|
||||
role: OrgRole;
|
||||
}
|
||||
Reference in New Issue
Block a user