UNPKG

business-as-code

Version:

Primitives for expressing business logic and processes as code

688 lines (606 loc) 17 kB
/** * Business Roles - Bridges digital-workers and ai-database authorization * * Connects: * - WorkerRole (business role: CEO, Engineer, Manager) * - Authorization Role (FGA/RBAC: permissions, access control) * - Task Assignment (who handles what in workflows/processes) * * @packageDocumentation */ import type { Role as OrgRole, RoleType as OrgRoleType, RoleWorkerType } from 'org.ai' import type { Worker, WorkerRef } from 'digital-workers' // Re-export for convenience export type { Worker, WorkerRef } from 'digital-workers' export type { OrgRole, OrgRoleType, RoleWorkerType } // ============================================================================= // Business Role - Bridges Worker Role and Authorization // ============================================================================= /** * Business role type - the function of a worker in the organization */ export type BusinessRoleType = // Executive | 'ceo' | 'cto' | 'cfo' | 'coo' | 'cmo' | 'cpo' // Management | 'director' | 'manager' | 'lead' | 'supervisor' // Individual Contributors | 'engineer' | 'designer' | 'analyst' | 'specialist' | 'coordinator' // Operations | 'operator' | 'agent' | 'assistant' // Custom | string /** * Business Role - extends org.ai Role with authorization and task capabilities * * Composes with org.ai Role to provide additional business-specific properties * like authorization permissions, task capabilities, and compensation. * * @example * ```ts * const engineeringManager: BusinessRole = { * id: 'role_eng_manager', * name: 'Engineering Manager', * type: 'manager', * department: 'Engineering', * description: 'Leads engineering team and makes technical decisions', * * // From org.ai Role * skills: ['TypeScript', 'Architecture', 'Team Leadership'], * * // Business responsibilities * responsibilities: [ * 'Lead engineering team', * 'Make architecture decisions', * 'Conduct code reviews', * ], * * // Authorization permissions (from FGA) * permissions: { * repository: ['read', 'edit', 'act:merge', 'act:deploy'], * project: ['read', 'edit', 'manage'], * team: ['read', 'edit'], * }, * * // Task capabilities * canHandle: ['code-review', 'architecture-decision', 'deployment-approval'], * canDelegate: ['code-review', 'testing'], * canApprove: ['pull-request', 'deployment', 'budget-under-5k'], * * // Worker type preference * workerType: 'human', * } * ``` */ export interface BusinessRole extends Omit<OrgRole, 'permissions'> { /** Role type classification (overrides OrgRole.type with business-specific types) */ type: BusinessRoleType /** * Authorization permissions by resource type * * Maps resource types to allowed actions: * - 'read', 'edit', 'delete', 'manage' (standard) * - 'act:*' or 'act:verb' (domain-specific verbs) * * Note: This differs from OrgRole.permissions which is string[] * * @example * ```ts * permissions: { * document: ['read', 'edit'], * invoice: ['read', 'act:send', 'act:void'], * project: ['read', 'edit', 'manage'], * } * ``` */ permissions?: Record<string, string[]> /** Compensation band */ compensationBand?: string } // ============================================================================= // Task Assignment - Connects Workers to Tasks // ============================================================================= /** * Task status */ export type TaskStatus = | 'pending' | 'assigned' | 'in_progress' | 'blocked' | 'completed' | 'failed' | 'cancelled' /** * Task priority */ export type TaskPriority = 'low' | 'normal' | 'high' | 'urgent' | 'critical' /** * Task assignment - links a worker to a task * * @example * ```ts * const assignment: TaskAssignment = { * id: 'assign_123', * taskId: 'task_review_pr_456', * taskType: 'code-review', * * // Who is assigned * assignee: { type: 'worker', id: 'worker_alice' }, * role: 'role_eng_manager', * * // From what process/workflow * processId: 'process_code_review', * stepId: 'step_1_review', * * // Status and timing * status: 'in_progress', * priority: 'high', * assignedAt: new Date(), * dueAt: new Date(Date.now() + 24 * 60 * 60 * 1000), * * // Context * context: { * pullRequestId: 'pr_789', * repository: 'acme/webapp', * }, * } * ``` */ export interface TaskAssignment { /** Unique assignment ID */ id: string /** Task identifier */ taskId: string /** Task type (for routing) */ taskType: string /** Task description */ description?: string /** Who is assigned */ assignee: AssigneeRef /** Required role for this task */ role?: string /** Source process ID */ processId?: string /** Source workflow ID */ workflowId?: string /** Step ID within process/workflow */ stepId?: string /** Current status */ status: TaskStatus /** Priority */ priority?: TaskPriority /** When assigned */ assignedAt: Date /** Who assigned it */ assignedBy?: AssigneeRef /** Due date */ dueAt?: Date /** Started at */ startedAt?: Date /** Completed at */ completedAt?: Date /** Task context/data */ context?: Record<string, unknown> /** Task result */ result?: unknown /** Notes */ notes?: string /** Metadata */ metadata?: Record<string, unknown> } /** * Reference to an assignee (worker, team, or role) * * Compatible with digital-workers WorkerRef for worker assignments. */ export interface AssigneeRef { /** Type of assignee */ type: 'worker' | 'team' | 'role' /** Assignee ID */ id: string /** Display name */ name?: string } /** * Convert a digital-workers WorkerRef to an AssigneeRef */ export function workerRefToAssignee(workerRef: WorkerRef): AssigneeRef { const result: AssigneeRef = { type: 'worker', id: workerRef.id, } if (workerRef.name !== undefined) { result.name = workerRef.name } return result } /** * Convert an AssigneeRef to a digital-workers WorkerRef (if type is 'worker') */ export function assigneeToWorkerRef(assignee: AssigneeRef): WorkerRef | null { if (assignee.type !== 'worker') return null const result: WorkerRef = { id: assignee.id, } if (assignee.name !== undefined) { result.name = assignee.name } return result } // ============================================================================= // Role-Based Task Routing // ============================================================================= /** * Task routing rule - determines who handles what tasks * * @example * ```ts * const routingRules: TaskRoutingRule[] = [ * { * taskType: 'code-review', * requiredRole: 'engineer', * requiredLevel: 2, * requiredSkills: ['TypeScript'], * preferWorkerType: 'human', * }, * { * taskType: 'expense-approval', * requiredRole: 'manager', * amountThreshold: 1000, * escalateAbove: 5000, // Escalate to director above $5k * escalateTo: 'director', * }, * { * taskType: 'customer-inquiry', * requiredRole: 'agent', * preferWorkerType: 'ai', * fallbackTo: 'human', // Escalate to human if AI can't handle * }, * ] * ``` */ export interface TaskRoutingRule { /** Task type this rule applies to */ taskType: string /** Required role type */ requiredRole?: BusinessRoleType /** Minimum level required */ requiredLevel?: number /** Required skills */ requiredSkills?: string[] /** Required permissions */ requiredPermissions?: string[] /** Preferred worker type */ preferWorkerType?: 'ai' | 'human' | 'hybrid' /** Amount threshold (for approval tasks) */ amountThreshold?: number /** Amount above which to escalate */ escalateAbove?: number /** Role to escalate to */ escalateTo?: BusinessRoleType | string /** Fallback worker type if preferred unavailable */ fallbackTo?: 'ai' | 'human' /** Priority for this task type */ defaultPriority?: TaskPriority /** SLA in minutes */ slaMinutes?: number /** Additional conditions */ conditions?: Record<string, unknown> } // ============================================================================= // Workflow Role - Role within a specific workflow // ============================================================================= /** * Workflow role - defines a role within the context of a workflow * * @example * ```ts * const workflowRoles: WorkflowRole[] = [ * { * name: 'Requester', * description: 'Person who initiates the request', * canInitiate: true, * canView: ['all'], * }, * { * name: 'Approver', * description: 'Person who approves or rejects', * tasks: ['review', 'approve', 'reject'], * canView: ['details', 'history'], * requiredBusinessRole: 'manager', * }, * { * name: 'Processor', * description: 'Person who processes after approval', * tasks: ['process', 'complete'], * requiredBusinessRole: 'operator', * }, * ] * ``` */ export interface WorkflowRole { /** Role name within workflow */ name: string /** Description */ description?: string /** Can initiate this workflow */ canInitiate?: boolean /** Tasks this role handles */ tasks?: string[] /** What this role can view */ canView?: string[] /** Required business role */ requiredBusinessRole?: BusinessRoleType | string /** Required permissions */ requiredPermissions?: string[] /** Minimum level */ minLevel?: number } // ============================================================================= // Standard Business Roles // ============================================================================= /** * Standard business roles with typical permissions */ export const StandardBusinessRoles: Record<string, Partial<BusinessRole>> = { // Executive ceo: { type: 'ceo', name: 'Chief Executive Officer', level: 10, permissions: { '*': ['manage'] }, canApprove: ['*'], workerType: 'human', }, cto: { type: 'cto', name: 'Chief Technology Officer', level: 10, department: 'Technology', permissions: { technology: ['manage'], repository: ['manage'], infrastructure: ['manage'], }, canApprove: ['technical-decision', 'architecture', 'technology-budget'], workerType: 'human', }, cfo: { type: 'cfo', name: 'Chief Financial Officer', level: 10, department: 'Finance', permissions: { finance: ['manage'], budget: ['manage'], expense: ['manage'], }, canApprove: ['expense', 'budget', 'financial-decision'], workerType: 'human', }, // Management director: { type: 'director', level: 8, permissions: { team: ['manage'], project: ['manage'], budget: ['read', 'edit'], }, canApprove: ['hiring', 'budget-under-50k', 'project'], workerType: 'human', }, manager: { type: 'manager', level: 6, permissions: { team: ['read', 'edit'], project: ['read', 'edit', 'manage'], }, canApprove: ['expense-under-5k', 'time-off', 'code-review'], workerType: 'human', }, lead: { type: 'lead', level: 5, permissions: { team: ['read'], project: ['read', 'edit'], repository: ['read', 'edit', 'act:merge'], }, canDelegate: ['code-review', 'testing'], workerType: 'hybrid', }, // Individual Contributors engineer: { type: 'engineer', level: 3, department: 'Engineering', permissions: { repository: ['read', 'edit'], project: ['read'], }, canHandle: ['coding', 'code-review', 'bug-fix', 'testing'], workerType: 'hybrid', }, analyst: { type: 'analyst', level: 3, permissions: { data: ['read'], report: ['read', 'edit'], }, canHandle: ['data-analysis', 'reporting', 'research'], workerType: 'hybrid', }, // Operations agent: { type: 'agent', level: 2, permissions: { ticket: ['read', 'edit', 'act:respond', 'act:escalate'], customer: ['read'], }, canHandle: ['customer-inquiry', 'support-ticket', 'basic-troubleshooting'], workerType: 'ai', // AI-first }, assistant: { type: 'assistant', level: 1, permissions: { calendar: ['read', 'edit'], email: ['read', 'act:draft'], task: ['read', 'edit'], }, canHandle: ['scheduling', 'email-draft', 'task-management', 'research'], workerType: 'ai', // AI-first }, } // ============================================================================= // Helper Functions // ============================================================================= /** * Create a business role from a standard template */ export function createBusinessRole( id: string, template: keyof typeof StandardBusinessRoles, overrides?: Partial<BusinessRole> ): BusinessRole { const standard = StandardBusinessRoles[template] if (!standard) { throw new Error(`Unknown role template: ${template}`) } return { id, name: standard['name'] || template, type: standard['type'] || template, ...standard, ...overrides, } as BusinessRole } /** * Check if a role has permission for an action on a resource type */ export function hasPermission(role: BusinessRole, resourceType: string, action: string): boolean { if (!role.permissions) return false // Check wildcard permissions const wildcardPerms = role.permissions['*'] if (wildcardPerms) { if (wildcardPerms.includes('manage') || wildcardPerms.includes('*')) return true if (wildcardPerms.includes(action)) return true } // Check resource-specific permissions const resourcePerms = role.permissions[resourceType] if (!resourcePerms) return false // Check for exact match if (resourcePerms.includes(action)) return true // Check for 'manage' which includes all actions if (resourcePerms.includes('manage') || resourcePerms.includes('*')) return true // Check for act:* pattern if (action.startsWith('act:')) { if (resourcePerms.includes('act:*')) return true } return false } /** * Check if a role can handle a task type */ export function canHandleTask(role: BusinessRole, taskType: string): boolean { if (!role['canHandle']) return false return role['canHandle'].includes(taskType) || role['canHandle'].includes('*') } /** * Check if a role can approve a request type */ export function canApproveRequest(role: BusinessRole, requestType: string): boolean { if (!role['canApprove']) return false return role['canApprove'].includes(requestType) || role['canApprove'].includes('*') } /** * Check if a role can delegate a task type */ export function canDelegateTask(role: BusinessRole, taskType: string): boolean { if (!role['canDelegate']) return false return role['canDelegate'].includes(taskType) || role['canDelegate'].includes('*') } /** * Find the best role for a task based on routing rules */ export function findRoleForTask( taskType: string, rules: TaskRoutingRule[], context?: { amount?: number; skills?: string[] } ): TaskRoutingRule | undefined { const matchingRules = rules.filter((rule) => rule.taskType === taskType) if (matchingRules.length === 0) return undefined // If there's an amount and escalation rules, check those if (context?.amount) { for (const rule of matchingRules) { if (rule.escalateAbove && context.amount > rule.escalateAbove) { // Find the escalated rule const escalatedRule = rules.find( (r) => r.taskType === taskType && r.requiredRole === rule.escalateTo ) if (escalatedRule) return escalatedRule } } } // Return the first matching rule return matchingRules[0] } /** * Create a task assignment */ export function createTaskAssignment( taskId: string, taskType: string, assignee: AssigneeRef, options?: Partial< Omit<TaskAssignment, 'id' | 'taskId' | 'taskType' | 'assignee' | 'status' | 'assignedAt'> > ): TaskAssignment { return { id: `assign_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`, taskId, taskType, assignee, status: 'assigned', assignedAt: new Date(), priority: 'normal', ...options, } } /** * Transition task assignment status */ export function transitionTaskStatus( assignment: TaskAssignment, newStatus: TaskStatus, options?: { result?: unknown; notes?: string } ): TaskAssignment { const now = new Date() return { ...assignment, status: newStatus, ...(newStatus === 'in_progress' && !assignment.startedAt ? { startedAt: now } : {}), ...(newStatus === 'completed' || newStatus === 'failed' ? { completedAt: now } : {}), ...(options?.result ? { result: options.result } : {}), ...(options?.notes ? { notes: options.notes } : {}), } }