UNPKG

oneie

Version:

Build apps, websites, and AI agents in English. Zero-interaction setup for AI agents (Claude Code, Cursor, Windsurf). Download to your computer, run in the cloud, deploy to the edge. Open source and free forever.

1,445 lines (1,190 loc) 39 kB
--- title: Crm Saas dimension: things category: examples tags: ai, architecture, knowledge, ontology related_dimensions: connections, events, groups, knowledge, people scope: global created: 2025-11-03 updated: 2025-11-03 version: 1.0.0 ai_context: | This document is part of the things dimension in the examples category. Location: one/things/examples/enterprise/crm-saas.md Purpose: Documents building enterprise saas with the 6-dimension ontology Related dimensions: connections, events, groups, knowledge, people For AI agents: Read this to understand crm saas. --- # Building Enterprise SaaS with the 6-Dimension Ontology ## Introduction: Enterprise-Grade Architecture The ONE Platform's 6-dimension ontology provides a complete foundation for building enterprise SaaS platforms that scale from startups to global enterprises. Unlike traditional architectures that require hundreds of tables and complex schemas, the 6-dimension model delivers: **Key Enterprise Benefits:** - **Perfect Multi-Tenant Isolation:** Each customer organization has completely isolated data - **Clear Ownership & Governance:** Role-based access control with audit trails - **Infinite Scalability:** Add customers without schema changes or architectural refactoring - **Cost Attribution:** Per-organization billing, quotas, and usage tracking - **AI-Native Design:** Built-in knowledge layer for intelligent features - **Compliance Ready:** GDPR, SOC2, HIPAA-compatible architecture This example demonstrates building a complete CRM SaaS platform using the 6-dimension ontology, showcasing multi-tenancy, authorization, and enterprise features. --- ## The 6 Dimensions (Enterprise Version) ### 1. Organizations - Multi-Tenant Isolation Boundary Organizations are the fundamental partitioning unit for multi-tenant SaaS. Every resource (thing, connection, event, knowledge) belongs to exactly one organization, providing perfect data isolation. **Enterprise Value:** - Each customer company is an organization - Zero cross-tenant data leaks (enforced at database level) - Independent billing, quotas, and rate limits - Custom branding and feature flags per organization - Platform-level services with centralized infrastructure **Organization Schema:** ```typescript interface Organization { _id: Id<"organizations">; name: string; // "Acme Corporation" slug: string; // "acme-corp" (URL-friendly) domain?: string; // "crm.acme.com" (custom domain) status: "active" | "suspended" | "trial" | "cancelled"; plan: "starter" | "pro" | "enterprise"; // Resource Limits limits: { users: number; // Max users in organization storage: number; // GB apiCalls: number; // Per month cycle: number; // LLM calls per month }; // Current Usage usage: { users: number; storage: number; apiCalls: number; cycle: number; }; // Billing Integration billing: { customerId?: string; // Stripe customer ID subscriptionId?: string; // Stripe subscription ID billingEmail?: string; }; // Organization Settings settings: { allowSignups: boolean; requireEmailVerification: boolean; enableTwoFactor: boolean; customBranding?: { logo?: string; primaryColor?: string; accentColor?: string; }; }; createdAt: number; updatedAt: number; trialEndsAt?: number; } ``` ### 2. People - Authorization & Governance Layer People define who can do what within the system. In the 6-dimension architecture, people are represented as things with `type: 'creator'` and a `role` property, providing a flexible yet type-safe authorization model. **Four Enterprise Roles:** 1. **Platform Owner** - Owns the entire SaaS platform infrastructure - Can access all organizations (for support and debugging) - Manages platform-level services and billing - Revenue: 100% of platform fees + revenue share from organizations 2. **Org Owner** - Owns and manages one or more customer organizations - Full control over users, permissions, and billing within their org - Can customize AI agents, branding, and features - Revenue: Percentage share of org subscription fees 3. **Org User** - Works within a specific organization - Limited permissions defined by org owner - Can create content, manage leads, run AI agents (within quotas) - No billing or administrative access 4. **Customer** - External user consuming services (e.g., lead in CRM) - No administrative access - Purchases products or services - Tracked for analytics and billing **Person Schema (as Thing):** ```typescript interface PersonThing { _id: Id<"things">; thingType: "creator"; name: string; // "Jane CEO" organizationId: Id<"organizations">; properties: { email: string; username: string; displayName: string; // CRITICAL: Role determines access level role: "platform_owner" | "org_owner" | "org_user" | "customer"; // Organization memberships (can belong to multiple orgs) organizations: Id<"organizations">[]; permissions?: string[]; // Custom permissions array // Profile information bio?: string; avatar?: string; timezone?: string; // Authentication metadata authProvider?: string; lastLoginAt?: number; }; status: "active" | "inactive" | "suspended"; createdAt: number; updatedAt: number; } ``` ### 3. Things - Domain Entities Things represent all entities in your CRM: leads, accounts, opportunities, AI agents, products, etc. The 66 pre-defined thing types cover most use cases, with extensibility via metadata. **Relevant Thing Types for CRM:** - `creator` - People (employees, customers) - `sales_agent` - AI-powered sales assistants - `support_agent` - AI-powered support agents - `lead` - Sales leads - `account` - Customer accounts - `opportunity` - Sales opportunities - `product` - Products or services - `task` - Action items - `email` - Email messages - `note` - Notes and memos ### 4. Connections - Business Relationships Connections define how entities relate to each other. In a CRM, this includes ownership, communication, task assignment, and deal stages. **Relevant Connection Types for CRM:** - `owns` - Person owns account/opportunity - `member_of` - Person is member of organization - `assigned_to` - Task assigned to person - `communicated` - Agent communicated with lead - `related_to` - Opportunity related to account - `purchased` - Customer purchased product ### 5. Events - Complete Audit Trail Events record every action in the system, providing compliance, analytics, and debugging capabilities. **Key Event Types for CRM:** - `user_created` - New user registered - `org_created` - New organization created - `communication_event` - Email/call/meeting logged - `deal_stage_changed` - Opportunity progressed - `task_completed` - Task marked done - `content_viewed` - User viewed content - `cycle_request` - AI agent invoked ### 6. Knowledge - Intelligence Layer Knowledge stores vectors, embeddings, and semantic search capabilities, enabling AI agents to provide context-aware assistance. **Knowledge Types:** - `label` - Tags and categories - `vector` - Embeddings for semantic search - `chunk` - Text chunks for RAG (Retrieval Augmented Generation) - `summary` - Generated summaries --- ## Example: Building a CRM SaaS Platform This complete example demonstrates implementing a multi-tenant CRM SaaS using the 6-dimension ontology. ### Step 1: Customer Signs Up (Create Organization) When a new customer signs up, create their organization with appropriate limits and settings. ```typescript import { mutation } from "./_generated/server"; import { v } from "convex/values"; export const createOrganization = mutation({ args: { name: v.string(), ownerEmail: v.string(), plan: v.optional( v.union(v.literal("starter"), v.literal("pro"), v.literal("enterprise")), ), }, handler: async (ctx, args) => { // Define plan limits const planLimits = { starter: { users: 5, storage: 10, apiCalls: 10000, cycle: 5000 }, pro: { users: 25, storage: 100, apiCalls: 100000, cycle: 50000 }, enterprise: { users: 1000, storage: 1000, apiCalls: 1000000, cycle: 500000, }, }; const plan = args.plan || "starter"; const slug = args.name.toLowerCase().replace(/[^a-z0-9]+/g, "-"); // Create organization const orgId = await ctx.db.insert("organizations", { name: args.name, slug, status: "trial", plan, limits: planLimits[plan], usage: { users: 0, storage: 0, apiCalls: 0, cycle: 0 }, billing: { billingEmail: args.ownerEmail }, settings: { allowSignups: true, requireEmailVerification: true, enableTwoFactor: false, }, createdAt: Date.now(), updatedAt: Date.now(), trialEndsAt: Date.now() + 14 * 24 * 60 * 60 * 1000, // 14-day trial }); // Create org owner (person) const ownerId = await ctx.db.insert("things", { thingType: "creator", name: args.ownerEmail.split("@")[0], organizationId: orgId, properties: { email: args.ownerEmail, username: args.ownerEmail.split("@")[0], displayName: args.ownerEmail.split("@")[0], role: "org_owner", organizations: [orgId], permissions: ["*"], // Full access }, status: "active", createdAt: Date.now(), updatedAt: Date.now(), }); // Create membership connection await ctx.db.insert("connections", { fromThingId: ownerId, toThingId: orgId as any, // Note: In practice, you'd use a thing representing the org relationshipType: "member_of", organizationId: orgId, metadata: { role: "org_owner" }, createdAt: Date.now(), }); // Log organization creation event await ctx.db.insert("events", { eventType: "org_created", actorId: ownerId, organizationId: orgId, metadata: { plan, trialDays: 14, }, timestamp: Date.now(), }); // Increment usage await ctx.db.patch(orgId, { usage: { users: 1, storage: 0, apiCalls: 0, cycle: 0 }, }); return { orgId, ownerId }; }, }); ``` ### Step 2: Create AI Sales Agent Set up an AI-powered sales agent for the organization. ```typescript export const createSalesAgent = mutation({ args: { orgId: v.id("organizations"), name: v.string(), systemPrompt: v.optional(v.string()), }, handler: async (ctx, args) => { // Authorization: Require org owner await requireOrgAccess(ctx, args.orgId, "org_owner"); const userId = await getUserId(ctx); // Default system prompt if not provided const defaultPrompt = `You are a friendly and professional sales assistant for ${args.name}. Your goals are to: 1. Qualify leads by understanding their needs and budget 2. Provide helpful information about products and services 3. Schedule meetings with the sales team when appropriate 4. Maintain a warm, consultative tone Always be helpful, never pushy. Focus on understanding the customer's challenges before pitching solutions.`; // Create sales agent const agentId = await ctx.db.insert("things", { thingType: "sales_agent", name: args.name, organizationId: args.orgId, properties: { systemPrompt: args.systemPrompt || defaultPrompt, temperature: 0.7, maxTokens: 1000, model: "claude-3-5-sonnet-20250101", capabilities: ["email", "chat", "calendar_scheduling"], }, status: "active", createdAt: Date.now(), updatedAt: Date.now(), }); // Create ownership connection await ctx.db.insert("connections", { fromThingId: userId, toThingId: agentId, relationshipType: "owns", organizationId: args.orgId, metadata: {}, createdAt: Date.now(), }); // Log agent creation await ctx.db.insert("events", { eventType: "agent_created", actorId: userId, thingId: agentId, organizationId: args.orgId, metadata: { agentType: "sales_agent" }, timestamp: Date.now(), }); return agentId; }, }); ``` ### Step 3: Import Leads Add leads to the CRM system. ```typescript export const createLead = mutation({ args: { orgId: v.id("organizations"), name: v.string(), email: v.string(), company: v.optional(v.string()), phone: v.optional(v.string()), budget: v.optional(v.number()), source: v.optional(v.string()), }, handler: async (ctx, args) => { // Authorization: Require org user or owner await requireOrgAccess(ctx, args.orgId, "org_user"); const userId = await getUserId(ctx); // Create lead as a thing const leadId = await ctx.db.insert("things", { thingType: "creator", // Using creator for customer/lead name: args.name, organizationId: args.orgId, properties: { email: args.email, username: args.email, displayName: args.name, role: "customer", company: args.company, phone: args.phone, budget: args.budget, source: args.source || "manual", status: "new", qualificationScore: 0, }, status: "active", createdAt: Date.now(), updatedAt: Date.now(), }); // Assign lead to user await ctx.db.insert("connections", { fromThingId: userId, toThingId: leadId, relationshipType: "owns", organizationId: args.orgId, metadata: { assignedAt: Date.now() }, createdAt: Date.now(), }); // Log lead creation await ctx.db.insert("events", { eventType: "user_created", actorId: userId, thingId: leadId, organizationId: args.orgId, metadata: { leadSource: args.source || "manual", company: args.company, }, timestamp: Date.now(), }); // Generate knowledge chunks for RAG const leadContext = `Lead: ${args.name} from ${ args.company || "unknown company" }. Email: ${args.email}. Budget: $${args.budget || "unknown"}. Source: ${ args.source || "manual" }.`; await generateKnowledge(ctx, { orgId: args.orgId, sourceThingId: leadId, text: leadContext, }); return leadId; }, }); ``` ### Step 4: AI Agent Communicates with Lead AI sales agent sends personalized outreach based on lead profile. ```typescript export const sendAIOutreach = mutation({ args: { orgId: v.id("organizations"), agentId: v.id("things"), leadId: v.id("things"), }, handler: async (ctx, args) => { // Authorization await requireOrgAccess(ctx, args.orgId, "org_user"); // Get agent and lead details const agent = await ctx.db.get(args.agentId); const lead = await ctx.db.get(args.leadId); if (!agent || agent.thingType !== "sales_agent") { throw new Error("Invalid sales agent"); } if (!lead) { throw new Error("Lead not found"); } // Query knowledge for relevant context const relevantContext = await queryKnowledge(ctx, { organizationId: args.orgId, query: `sales outreach ${lead.properties.company} ${lead.properties.source}`, k: 5, }); // Generate personalized email using LLM const emailContent = await generateEmail(ctx, { systemPrompt: agent.properties.systemPrompt, leadName: lead.name, leadCompany: lead.properties.company, context: relevantContext, }); // Create email thing const emailId = await ctx.db.insert("things", { thingType: "email", name: `Outreach to ${lead.name}`, organizationId: args.orgId, properties: { from: agent.name, to: lead.properties.email, subject: emailContent.subject, body: emailContent.body, status: "sent", sentAt: Date.now(), }, status: "active", createdAt: Date.now(), updatedAt: Date.now(), }); // Create communication connection await ctx.db.insert("connections", { fromThingId: args.agentId, toThingId: args.leadId, relationshipType: "communicated", organizationId: args.orgId, metadata: { protocol: "email", emailId, subject: emailContent.subject, }, createdAt: Date.now(), }); // Log communication event await ctx.db.insert("events", { eventType: "communication_event", actorId: args.agentId, thingId: args.leadId, organizationId: args.orgId, metadata: { protocol: "email", messageType: "outreach", emailId, sentiment: "positive", }, timestamp: Date.now(), }); // Update cycle usage await incrementUsage(ctx, args.orgId, "cycle", 1); return { emailId, emailContent }; }, }); ``` ### Step 5: Track Lead Progression Monitor and update lead status as they move through the sales pipeline. ```typescript export const updateLeadStatus = mutation({ args: { orgId: v.id("organizations"), leadId: v.id("things"), newStatus: v.string(), notes: v.optional(v.string()), }, handler: async (ctx, args) => { await requireOrgAccess(ctx, args.orgId, "org_user"); const userId = await getUserId(ctx); const lead = await ctx.db.get(args.leadId); if (!lead || lead.organizationId !== args.orgId) { throw new Error("Lead not found or access denied"); } const oldStatus = lead.properties.status; // Update lead status await ctx.db.patch(args.leadId, { properties: { ...lead.properties, status: args.newStatus, lastUpdatedBy: userId, }, updatedAt: Date.now(), }); // Create note if provided if (args.notes) { const noteId = await ctx.db.insert("things", { thingType: "note", name: `Note: ${lead.name} status change`, organizationId: args.orgId, properties: { content: args.notes, authorId: userId, }, status: "active", createdAt: Date.now(), updatedAt: Date.now(), }); // Link note to lead await ctx.db.insert("connections", { fromThingId: noteId, toThingId: args.leadId, relationshipType: "related_to", organizationId: args.orgId, metadata: {}, createdAt: Date.now(), }); } // Log status change event await ctx.db.insert("events", { eventType: "deal_stage_changed", actorId: userId, thingId: args.leadId, organizationId: args.orgId, metadata: { oldStatus, newStatus: args.newStatus, notes: args.notes, }, timestamp: Date.now(), }); return { success: true, oldStatus, newStatus: args.newStatus }; }, }); ``` ### Step 6: Query Organization Data Retrieve CRM data with proper multi-tenant scoping. ```typescript import { query } from "./_generated/server"; export const getLeads = query({ args: { orgId: v.id("organizations"), status: v.optional(v.string()), limit: v.optional(v.number()), }, handler: async (ctx, args) => { // Authorization: Require org access await requireOrgAccess(ctx, args.orgId, "org_user"); // Query leads scoped to organization let leadsQuery = ctx.db .query("things") .withIndex("by_org_type", (q) => q.eq("organizationId", args.orgId).eq("thingType", "creator"), ) .filter((q) => q.eq(q.field("properties").role, "customer")); // Optional status filter if (args.status) { leadsQuery = leadsQuery.filter((q) => q.eq(q.field("properties").status, args.status), ); } const leads = await leadsQuery.order("desc").take(args.limit || 50); // Get assigned users for each lead const leadsWithOwners = await Promise.all( leads.map(async (lead) => { const ownerConnection = await ctx.db .query("connections") .withIndex("by_to", (q) => q.eq("toThingId", lead._id)) .filter((q) => q.eq(q.field("relationshipType"), "owns")) .first(); const owner = ownerConnection ? await ctx.db.get(ownerConnection.fromThingId) : null; return { ...lead, owner: owner ? { id: owner._id, name: owner.name, email: owner.properties.email, } : null, }; }), ); return leadsWithOwners; }, }); export const getOrganizationStats = query({ args: { orgId: v.id("organizations"), }, handler: async (ctx, args) => { await requireOrgAccess(ctx, args.orgId, "org_owner"); const org = await ctx.db.get(args.orgId); if (!org) { throw new Error("Organization not found"); } // Count entities by type const leads = await ctx.db .query("things") .withIndex("by_org_type", (q) => q.eq("organizationId", args.orgId).eq("thingType", "creator"), ) .filter((q) => q.eq(q.field("properties").role, "customer")) .collect(); const agents = await ctx.db .query("things") .withIndex("by_org_type", (q) => q.eq("organizationId", args.orgId).eq("thingType", "sales_agent"), ) .collect(); const events = await ctx.db .query("events") .withIndex("by_org", (q) => q.eq("organizationId", args.orgId)) .collect(); // Calculate conversion metrics const convertedLeads = leads.filter( (lead) => lead.properties.status === "won", ).length; const conversionRate = leads.length > 0 ? (convertedLeads / leads.length) * 100 : 0; return { organization: { name: org.name, plan: org.plan, status: org.status, }, usage: org.usage, limits: org.limits, stats: { totalLeads: leads.length, convertedLeads, conversionRate: Math.round(conversionRate * 100) / 100, activeAgents: agents.filter((a) => a.status === "active").length, totalEvents: events.length, }, }; }, }); ``` --- ## Multi-Tenancy Benefits ### 1. Perfect Data Isolation The 6-dimension architecture enforces data isolation at the database level through organization scoping. **Example: Cross-Org Query Prevention** ```typescript // SAFE: All queries automatically scoped to organization export const getLeads = query({ args: { orgId: v.id("organizations") }, handler: async (ctx, args) => { // This query can ONLY return leads from args.orgId const leads = await ctx.db .query("things") .withIndex("by_org_type", (q) => q .eq("organizationId", args.orgId) // Enforces isolation .eq("thingType", "creator"), ) .collect(); return leads; }, }); // UNSAFE PATTERN (avoided in our architecture) // const allLeads = await ctx.db.query("things").collect(); // This would violate multi-tenancy! ``` **Benefits:** - Zero chance of cross-organization data leaks - Compliance with data privacy regulations (GDPR, CCPA) - Customer trust and confidence - Simplified security audits ### 2. Independent Scaling & Resource Management Each organization has independent quotas and billing. ```typescript export const checkQuota = async ( ctx: any, orgId: Id<"organizations">, resourceType: "users" | "storage" | "apiCalls" | "cycle", ) => { const org = await ctx.db.get(orgId); if (!org) { throw new Error("Organization not found"); } const current = org.usage[resourceType]; const limit = org.limits[resourceType]; if (current >= limit) { throw new Error( `${resourceType} quota exceeded. Current: ${current}, Limit: ${limit}`, ); } return { current, limit, available: limit - current, percentUsed: Math.round((current / limit) * 100), }; }; export const incrementUsage = async ( ctx: any, orgId: Id<"organizations">, resourceType: "users" | "storage" | "apiCalls" | "cycle", amount: number = 1, ) => { const org = await ctx.db.get(orgId); if (!org) { throw new Error("Organization not found"); } // Check quota before incrementing await checkQuota(ctx, orgId, resourceType); // Increment usage await ctx.db.patch(orgId, { usage: { ...org.usage, [resourceType]: org.usage[resourceType] + amount, }, }); }; ``` **Benefits:** - Per-organization billing and invoicing - Prevent resource abuse and overages - Upgrade/downgrade plans without affecting others - Clear cost attribution for platform economics ### 3. Customization & White-Labeling Organizations can customize their experience independently. ```typescript export const updateBranding = mutation({ args: { orgId: v.id("organizations"), logo: v.optional(v.string()), primaryColor: v.optional(v.string()), accentColor: v.optional(v.string()), }, handler: async (ctx, args) => { await requireOrgAccess(ctx, args.orgId, "org_owner"); const org = await ctx.db.get(args.orgId); if (!org) { throw new Error("Organization not found"); } await ctx.db.patch(args.orgId, { settings: { ...org.settings, customBranding: { logo: args.logo || org.settings.customBranding?.logo, primaryColor: args.primaryColor || org.settings.customBranding?.primaryColor, accentColor: args.accentColor || org.settings.customBranding?.accentColor, }, }, updatedAt: Date.now(), }); return { success: true }; }, }); export const customizeAIAgent = mutation({ args: { orgId: v.id("organizations"), agentId: v.id("things"), systemPrompt: v.string(), temperature: v.optional(v.number()), }, handler: async (ctx, args) => { await requireOrgAccess(ctx, args.orgId, "org_owner"); const agent = await ctx.db.get(args.agentId); if (!agent || agent.organizationId !== args.orgId) { throw new Error("Agent not found or access denied"); } await ctx.db.patch(args.agentId, { properties: { ...agent.properties, systemPrompt: args.systemPrompt, temperature: args.temperature || agent.properties.temperature, customizedBy: await getUserId(ctx), customizedAt: Date.now(), }, updatedAt: Date.now(), }); return { success: true }; }, }); ``` **Benefits:** - Each customer can brand their CRM instance - Custom AI agent personalities per organization - Feature flags and custom workflows - White-label deployment options ### 4. Platform Revenue & Economics Clear revenue attribution through the organization dimension. ```typescript export const calculateRevenue = query({ args: { startDate: v.number(), endDate: v.number(), }, handler: async (ctx, args) => { // Platform owner only const userId = await getUserId(ctx); const user = await ctx.db.get(userId); if (user?.properties.role !== "platform_owner") { throw new Error("Platform owner access required"); } // Get all active organizations const orgs = await ctx.db .query("organizations") .withIndex("by_status", (q) => q.eq("status", "active")) .collect(); // Calculate revenue by plan const revenueByPlan = { starter: { count: 0, mrr: 0 }, pro: { count: 0, mrr: 0 }, enterprise: { count: 0, mrr: 0 }, }; const planPricing = { starter: 29, pro: 99, enterprise: 499, }; orgs.forEach((org) => { revenueByPlan[org.plan].count += 1; revenueByPlan[org.plan].mrr += planPricing[org.plan]; }); // Calculate cycle costs const totalCycleCalls = orgs.reduce( (sum, org) => sum + org.usage.cycle, 0, ); const costPerCycle = 0.01; // $0.01 per call const cycleCosts = totalCycleCalls * costPerCycle; // Platform revenue = subscription fees + cycle markup const subscriptionRevenue = Object.values(revenueByPlan).reduce( (sum, plan) => sum + plan.mrr, 0, ); const cycleRevenue = cycleCosts * 0.3; // 30% markup const totalRevenue = subscriptionRevenue + cycleRevenue; return { period: { startDate: args.startDate, endDate: args.endDate }, organizations: { total: orgs.length, byPlan: revenueByPlan, }, revenue: { subscriptions: subscriptionRevenue, cycle: cycleRevenue, total: totalRevenue, }, costs: { cycle: cycleCosts, }, profit: totalRevenue - cycleCosts, }; }, }); ``` **Benefits:** - Clear revenue attribution per organization - Track costs (cycle, storage, API calls) - Revenue sharing between platform and org owners - Financial analytics for business decisions --- ## Security & Compliance ### GDPR Compliance - Right to Erasure The organization dimension makes GDPR compliance straightforward. ```typescript export const deleteOrganizationData = mutation({ args: { orgId: v.id("organizations"), confirmationCode: v.string(), }, handler: async (ctx, args) => { // Platform owner or org owner only const userId = await getUserId(ctx); const user = await ctx.db.get(userId); const isPlatformOwner = user?.properties.role === "platform_owner"; const isOrgOwner = user?.properties.role === "org_owner" && user?.organizationId === args.orgId; if (!isPlatformOwner && !isOrgOwner) { throw new Error( "Unauthorized: Only platform or org owner can delete org data", ); } // Verify confirmation code const expectedCode = `DELETE-${args.orgId.slice(0, 8)}`; if (args.confirmationCode !== expectedCode) { throw new Error("Invalid confirmation code"); } // Delete all organization data const deletionCounts = { things: 0, connections: 0, events: 0, knowledge: 0, }; // Delete things const things = await ctx.db .query("things") .withIndex("by_org", (q) => q.eq("organizationId", args.orgId)) .collect(); for (const thing of things) { await ctx.db.delete(thing._id); deletionCounts.things += 1; } // Delete connections const connections = await ctx.db .query("connections") .withIndex("by_org", (q) => q.eq("organizationId", args.orgId)) .collect(); for (const connection of connections) { await ctx.db.delete(connection._id); deletionCounts.connections += 1; } // Delete events const events = await ctx.db .query("events") .withIndex("by_org", (q) => q.eq("organizationId", args.orgId)) .collect(); for (const event of events) { await ctx.db.delete(event._id); deletionCounts.events += 1; } // Delete knowledge const knowledge = await ctx.db .query("knowledge") .withIndex("by_org", (q) => q.eq("organizationId", args.orgId)) .collect(); for (const k of knowledge) { await ctx.db.delete(k._id); deletionCounts.knowledge += 1; } // Delete organization itself await ctx.db.delete(args.orgId); // Log deletion (to a separate audit table not shown here) console.log( `Organization ${args.orgId} deleted by ${userId}`, deletionCounts, ); return { success: true, deletionCounts, message: "All organization data permanently deleted", }; }, }); ``` ### Audit Trails - Complete Event Logging Every action is logged with actor, timestamp, and organization context. ```typescript export const getAuditLog = query({ args: { orgId: v.id("organizations"), startDate: v.optional(v.number()), endDate: v.optional(v.number()), eventType: v.optional(v.string()), actorId: v.optional(v.id("things")), limit: v.optional(v.number()), }, handler: async (ctx, args) => { // Org owner only await requireOrgAccess(ctx, args.orgId, "org_owner"); // Query events for organization let eventsQuery = ctx.db .query("events") .withIndex("by_org", (q) => q.eq("organizationId", args.orgId)) .order("desc"); // Apply filters if (args.eventType) { eventsQuery = eventsQuery.filter((q) => q.eq(q.field("eventType"), args.eventType), ); } if (args.actorId) { eventsQuery = eventsQuery.filter((q) => q.eq(q.field("actorId"), args.actorId), ); } if (args.startDate) { eventsQuery = eventsQuery.filter((q) => q.gte(q.field("timestamp"), args.startDate!), ); } if (args.endDate) { eventsQuery = eventsQuery.filter((q) => q.lte(q.field("timestamp"), args.endDate!), ); } const events = await eventsQuery.take(args.limit || 100); // Enrich with actor and thing details const enrichedEvents = await Promise.all( events.map(async (event) => { const actor = await ctx.db.get(event.actorId); const thing = event.thingId ? await ctx.db.get(event.thingId) : null; return { ...event, actor: actor ? { id: actor._id, name: actor.name, email: actor.properties.email, role: actor.properties.role, } : null, thing: thing ? { id: thing._id, name: thing.name, type: thing.thingType, } : null, }; }), ); return enrichedEvents; }, }); ``` ### Access Control - Role-Based Permissions Implement fine-grained access control using the people dimension. ```typescript // Authorization middleware export async function requireOrgAccess( ctx: any, orgId: Id<"organizations">, requiredRole: "org_owner" | "org_user", ) { const userId = await getUserId(ctx); const user = await ctx.db.get(userId); if (!user) { throw new Error("User not found"); } // Platform owner can access everything if (user.properties.role === "platform_owner") { return true; } // Check if user belongs to organization const membership = await ctx.db .query("connections") .withIndex("from_type", (q) => q.eq("fromThingId", userId).eq("relationshipType", "member_of"), ) .filter((q) => q.eq(q.field("metadata").organizationId, orgId)) .first(); if (!membership) { throw new Error("User not member of organization"); } // Check role const userRole = user.properties.role; if (requiredRole === "org_owner" && userRole !== "org_owner") { throw new Error("Organization owner access required"); } return true; } // Permission check for specific actions export async function checkPermission( ctx: any, userId: Id<"things">, permission: string, ): Promise<boolean> { const user = await ctx.db.get(userId); if (!user) { return false; } // Platform owner has all permissions if (user.properties.role === "platform_owner") { return true; } // Check if user has specific permission const permissions = user.properties.permissions || []; // Wildcard permission if (permissions.includes("*")) { return true; } // Exact match if (permissions.includes(permission)) { return true; } // Pattern match (e.g., "leads.*" matches "leads.create", "leads.update") const hasMatch = permissions.some((p) => { if (p.endsWith("*")) { const prefix = p.slice(0, -1); return permission.startsWith(prefix); } return false; }); return hasMatch; } ``` ### Data Encryption & Privacy Implement organization-scoped encryption for sensitive data. ```typescript export const encryptSensitiveData = async ( ctx: any, orgId: Id<"organizations">, data: string, ): Promise<string> => { // In production, use org-specific encryption keys // Stored securely in a key management service (KMS) const orgKey = await getOrganizationEncryptionKey(orgId); // Encrypt data using AES-256 const encrypted = await encrypt(data, orgKey); return encrypted; }; export const decryptSensitiveData = async ( ctx: any, orgId: Id<"organizations">, encryptedData: string, ): Promise<string> => { const orgKey = await getOrganizationEncryptionKey(orgId); const decrypted = await decrypt(encryptedData, orgKey); return decrypted; }; // Example: Storing encrypted customer data export const createLeadWithEncryption = mutation({ args: { orgId: v.id("organizations"), name: v.string(), email: v.string(), phone: v.optional(v.string()), ssn: v.optional(v.string()), // Sensitive data }, handler: async (ctx, args) => { await requireOrgAccess(ctx, args.orgId, "org_user"); const userId = await getUserId(ctx); // Encrypt sensitive data const encryptedSSN = args.ssn ? await encryptSensitiveData(ctx, args.orgId, args.ssn) : undefined; const leadId = await ctx.db.insert("things", { thingType: "creator", name: args.name, organizationId: args.orgId, properties: { email: args.email, phone: args.phone, encryptedSSN, // Stored encrypted role: "customer", }, status: "active", createdAt: Date.now(), updatedAt: Date.now(), }); return leadId; }, }); ``` --- ## Conclusion The 6-dimension ontology provides a complete, production-ready foundation for building enterprise SaaS platforms: **Key Takeaways:** 1. **Organizations** provide perfect multi-tenant isolation at the database level 2. **People** enable role-based access control and governance 3. **Things, Connections, Events** model all business entities and relationships 4. **Knowledge** powers AI-driven features with RAG and semantic search 5. **Security & Compliance** are built-in, not bolted-on 6. **Infinite Scalability** without schema changes or architectural refactoring **What You Can Build:** - CRM platforms (demonstrated in this example) - Project management tools - E-commerce marketplaces - Content management systems - Customer support platforms - Analytics and BI tools - And any other multi-tenant SaaS application **Next Steps:** 1. Review the [6-Dimension Ontology specification](/Users/toc/Server/ONE/one/knowledge/ontology.md) 2. Explore the [Architecture documentation](/Users/toc/Server/ONE/one/knowledge/architecture.md) 3. Study additional [enterprise examples](/Users/toc/Server/ONE/one/examples/enterprise/) 4. Start building your own SaaS platform with the ONE Platform The 6-dimension architecture proves you don't need hundreds of tables or complex schemas to build enterprise-grade software. You need clarity, simplicity, and a reality-aware model that scales infinitely. **Build smarter. Build with ONE.**