business-as-code
Version:
Primitives for expressing business logic and processes as code
688 lines (606 loc) • 17 kB
text/typescript
/**
* 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 } : {}),
}
}