business-as-code
Version:
Primitives for expressing business logic and processes as code
907 lines (755 loc) • 20.6 kB
text/typescript
/**
* Organization Structure - Flows to FGA/RBAC
*
* Defines the complete organizational hierarchy:
*
* Organization
* └── Department
* └── Team
* └── Position (Role + Worker)
* └── Permissions (FGA/RBAC)
*
* This structure enables:
* - Hierarchical permission inheritance
* - Role-based task assignment
* - Approval chains based on org structure
* - Resource access control based on department/team
*
* @packageDocumentation
*/
import type { BusinessRole, TaskRoutingRule, WorkflowRole } from './roles.js'
// =============================================================================
// Organization Hierarchy
// =============================================================================
/**
* Organization - top-level business entity
*
* @example
* ```ts
* const acme: Organization = {
* id: 'org_acme',
* name: 'Acme Corp',
* domain: 'acme.com',
* industry: 'technology',
*
* // Hierarchy
* departments: [engineering, sales, support],
*
* // Global settings
* settings: {
* defaultCurrency: 'USD',
* timezone: 'America/Los_Angeles',
* workWeek: ['monday', 'tuesday', 'wednesday', 'thursday', 'friday'],
* },
*
* // Resource hierarchy for FGA
* resourceHierarchy: {
* organization: { children: ['department', 'project', 'repository'] },
* department: { parent: 'organization', children: ['team'] },
* team: { parent: 'department', children: ['position'] },
* },
* }
* ```
*/
export interface Organization {
/** Unique identifier */
id: string
/** Organization name */
name: string
/** Primary domain */
domain?: string
/** Legal name (if different) */
legalName?: string
/** Industry/sector */
industry?: string
/** Mission statement */
mission?: string
/** Core values */
values?: string[]
/** Founded date */
foundedAt?: Date
/** Headquarters location */
headquarters?: Address
/** Organization settings */
settings?: OrganizationSettings
/** Departments */
departments?: Department[]
/** Standalone teams (not in departments) */
teams?: Team[]
/** Organization-wide roles */
roles?: BusinessRole[]
/** Resource hierarchy for FGA */
resourceHierarchy?: ResourceHierarchy
/** Global approval chains */
approvalChains?: ApprovalChain[]
/** Task routing rules */
routingRules?: TaskRoutingRule[]
/** Metadata */
metadata?: Record<string, unknown>
}
/**
* Organization settings
*/
export interface OrganizationSettings {
/** Default currency */
defaultCurrency?: string
/** Default timezone */
timezone?: string
/** Work week days */
workWeek?: string[]
/** Business hours */
businessHours?: {
start: string
end: string
timezone?: string
}
/** Fiscal year start month (1-12) */
fiscalYearStart?: number
/** Default language */
language?: string
/** Date format */
dateFormat?: string
}
/**
* Address
*/
export interface Address {
street?: string
city?: string
state?: string
postalCode?: string
country?: string
}
// =============================================================================
// Department
// =============================================================================
/**
* Department - major organizational division
*
* @example
* ```ts
* const engineering: Department = {
* id: 'dept_eng',
* name: 'Engineering',
* code: 'ENG',
*
* // Leadership
* head: { roleId: 'role_cto', positionId: 'pos_cto_jane' },
*
* // Teams
* teams: [platformTeam, productTeam, infraTeam],
*
* // Budget
* budget: {
* annual: 5000000,
* currency: 'USD',
* categories: {
* salaries: 3500000,
* tools: 200000,
* infrastructure: 800000,
* training: 100000,
* },
* },
*
* // FGA: Department-level permissions
* defaultPermissions: {
* repository: ['read'],
* project: ['read'],
* },
* }
* ```
*/
export interface Department {
/** Unique identifier */
id: string
/** Department name */
name: string
/** Short code (e.g., 'ENG', 'SALES') */
code?: string
/** Description */
description?: string
/** Department head position */
head?: PositionRef
/** Parent department (for sub-departments) */
parentId?: string
/** Teams within department */
teams?: Team[]
/** Department budget */
budget?: Budget
/** Cost center code */
costCenter?: string
/** Department goals */
goals?: string[]
/** Default permissions for department members */
defaultPermissions?: Record<string, string[]>
/** Department-specific roles */
roles?: BusinessRole[]
/** Department-specific routing rules */
routingRules?: TaskRoutingRule[]
/** Metadata */
metadata?: Record<string, unknown>
}
// =============================================================================
// Team
// =============================================================================
/**
* Team - working group within a department
*
* @example
* ```ts
* const platformTeam: Team = {
* id: 'team_platform',
* name: 'Platform Team',
* departmentId: 'dept_eng',
*
* // Leadership
* lead: { roleId: 'role_lead', positionId: 'pos_lead_bob' },
*
* // Members
* positions: [
* { id: 'pos_1', roleId: 'role_engineer', workerId: 'worker_alice' },
* { id: 'pos_2', roleId: 'role_engineer', workerId: 'worker_charlie' },
* { id: 'pos_3', roleId: 'role_engineer', workerId: null }, // Open position
* ],
*
* // Team resources (for FGA scoping)
* resources: {
* repositories: ['platform-core', 'platform-api'],
* projects: ['platform-v2'],
* },
*
* // Team-level permissions (inherited by members)
* defaultPermissions: {
* repository: ['read', 'edit', 'act:merge'],
* project: ['read', 'edit'],
* },
* }
* ```
*/
export interface Team {
/** Unique identifier */
id: string
/** Team name */
name: string
/** Parent department */
departmentId?: string
/** Description */
description?: string
/** Team lead position */
lead?: PositionRef
/** Team positions */
positions?: Position[]
/** Team objectives */
objectives?: string[]
/** Team resources (for FGA scoping) */
resources?: TeamResources
/** Team budget (if separate from department) */
budget?: Budget
/** Default permissions for team members */
defaultPermissions?: Record<string, string[]>
/** Workflow roles for this team */
workflowRoles?: WorkflowRole[]
/** Communication channels */
channels?: TeamChannels
/** Metadata */
metadata?: Record<string, unknown>
}
/**
* Team resources - scopes FGA permissions
*/
export interface TeamResources {
/** Owned repositories */
repositories?: string[]
/** Owned projects */
projects?: string[]
/** Owned products */
products?: string[]
/** Custom resource types */
[]: string[] | undefined
}
/**
* Team communication channels
*/
export interface TeamChannels {
slack?: string
teams?: string
email?: string
discord?: string
}
// =============================================================================
// Position - Role + Worker Assignment
// =============================================================================
/**
* Position - a role filled by a worker
*
* The position is the link between:
* - Business Role (responsibilities, permissions)
* - Worker (human or AI agent)
* - Location in org hierarchy (team → department → org)
*
* @example
* ```ts
* const seniorEngineerPosition: Position = {
* id: 'pos_se_123',
* title: 'Senior Software Engineer',
*
* // Role defines what this position can do
* roleId: 'role_senior_engineer',
*
* // Worker currently filling this position
* workerId: 'worker_alice',
* workerType: 'human',
*
* // Hierarchy
* teamId: 'team_platform',
* reportsTo: 'pos_lead_bob',
*
* // Position-specific permissions (added to role permissions)
* additionalPermissions: {
* 'repository:platform-core': ['manage'],
* },
*
* // Employment details
* startDate: new Date('2023-01-15'),
* status: 'active',
* }
* ```
*/
export interface Position {
/** Unique identifier */
id: string
/** Position title */
title: string
/** Business role this position requires */
roleId: string
/** Worker filling this position (null = open position) */
workerId?: string | null
/** Type of worker (human, agent, or either) */
workerType?: 'human' | 'agent' | 'any'
/** Team this position belongs to */
teamId?: string
/** Position this reports to */
reportsTo?: string
/** Direct reports (position IDs) */
directReports?: string[]
/** Position-specific additional permissions */
additionalPermissions?: Record<string, string[]>
/** Start date */
startDate?: Date
/** End date (for contractors/temporary) */
endDate?: Date
/** Position status */
status?: 'active' | 'open' | 'on-leave' | 'terminated'
/** Full-time equivalent (1.0 = full-time) */
fte?: number
/** Location */
location?: string
/** Remote/hybrid/onsite */
workModel?: 'remote' | 'hybrid' | 'onsite'
/** Compensation */
compensation?: Compensation
/** Metadata */
metadata?: Record<string, unknown>
}
/**
* Position reference
*/
export interface PositionRef {
positionId?: string
roleId?: string
workerId?: string
}
/**
* Compensation details
*/
export interface Compensation {
/** Base salary */
baseSalary?: number
/** Currency */
currency?: string
/** Pay frequency */
frequency?: 'hourly' | 'weekly' | 'biweekly' | 'monthly' | 'annual'
/** Bonus target percentage */
bonusTarget?: number
/** Equity grants */
equity?: {
type: 'options' | 'rsu' | 'shares'
amount: number
vestingSchedule?: string
}
/** Band/level */
band?: string
}
// =============================================================================
// Budget
// =============================================================================
/**
* Budget allocation
*/
export interface Budget {
/** Total annual budget */
annual?: number
/** Currency */
currency?: string
/** Budget period */
period?: string
/** Budget categories */
categories?: Record<string, number>
/** Spent to date */
spent?: number
/** Remaining */
remaining?: number
}
// =============================================================================
// Resource Hierarchy - FGA Integration
// =============================================================================
/**
* Resource hierarchy definition for FGA
*
* Defines how resources relate to each other for permission inheritance.
*
* @example
* ```ts
* const hierarchy: ResourceHierarchy = {
* // Organization is root
* organization: {
* children: ['department', 'project', 'repository'],
* },
*
* // Department inherits from org
* department: {
* parent: 'organization',
* children: ['team'],
* },
*
* // Team inherits from department
* team: {
* parent: 'department',
* children: ['position'],
* },
*
* // Project can be org-level or team-level
* project: {
* parent: 'organization',
* alternateParents: ['team'],
* children: ['document', 'task'],
* },
*
* // Repository can be org-level or team-level
* repository: {
* parent: 'organization',
* alternateParents: ['team'],
* },
* }
* ```
*/
export interface ResourceHierarchy {
[]: ResourceHierarchyNode
}
/**
* Node in resource hierarchy
*/
export interface ResourceHierarchyNode {
/** Primary parent resource type */
parent?: string
/** Alternative parent types */
alternateParents?: string[]
/** Child resource types */
children?: string[]
/** Whether permissions cascade down */
inheritPermissions?: boolean
/** Maximum depth (for nested resources) */
maxDepth?: number
}
// =============================================================================
// Approval Chains
// =============================================================================
/**
* Approval chain - defines who approves what
*
* @example
* ```ts
* const expenseApprovalChain: ApprovalChain = {
* id: 'chain_expense',
* name: 'Expense Approval',
* type: 'expense',
*
* levels: [
* {
* threshold: 1000,
* approvers: [{ type: 'direct-manager' }],
* },
* {
* threshold: 5000,
* approvers: [
* { type: 'direct-manager' },
* { type: 'role', roleId: 'director' },
* ],
* },
* {
* threshold: 25000,
* approvers: [
* { type: 'direct-manager' },
* { type: 'role', roleId: 'vp' },
* { type: 'role', roleId: 'cfo' },
* ],
* },
* ],
*
* // Escalation settings
* escalation: {
* afterHours: 24,
* escalateTo: 'skip-level-manager',
* },
* }
* ```
*/
export interface ApprovalChain {
/** Chain identifier */
id: string
/** Chain name */
name: string
/** What type of requests this chain handles */
type: string
/** Approval levels */
levels: ApprovalLevel[]
/** Escalation rules */
escalation?: EscalationRule
/** Active/inactive */
active?: boolean
/** Metadata */
metadata?: Record<string, unknown>
}
/**
* Approval level within a chain
*/
export interface ApprovalLevel {
/** Threshold amount (requests above this need this level) */
threshold?: number
/** Approvers at this level */
approvers: ApproverSpec[]
/** How many approvers needed */
requiredApprovals?: number
/** Whether approvals at this level are sequential or parallel */
approvalMode?: 'sequential' | 'parallel' | 'any'
/** SLA for this level (hours) */
slaHours?: number
}
/**
* Approver specification
*/
export interface ApproverSpec {
/** Approver type */
type: 'direct-manager' | 'skip-level-manager' | 'role' | 'position' | 'worker' | 'team'
/** Role ID (if type is 'role') */
roleId?: string
/** Position ID (if type is 'position') */
positionId?: string
/** Worker ID (if type is 'worker') */
workerId?: string
/** Team ID (if type is 'team') */
teamId?: string
}
/**
* Escalation rule
*/
export interface EscalationRule {
/** Hours before escalation */
afterHours: number
/** Who to escalate to */
escalateTo: 'skip-level-manager' | 'department-head' | 'role' | 'position'
/** Role ID if escalating to role */
roleId?: string
/** Position ID if escalating to position */
positionId?: string
/** Maximum escalations */
maxEscalations?: number
}
// =============================================================================
// Permission Resolution
// =============================================================================
/**
* Resolved permissions for a worker
*
* Combines:
* - Organization-level defaults
* - Department-level permissions
* - Team-level permissions
* - Role permissions
* - Position-specific permissions
*/
export interface ResolvedPermissions {
/** Worker ID */
workerId: string
/** Position ID */
positionId: string
/** Effective permissions by resource type */
permissions: Record<string, string[]>
/** Resource-specific permissions */
resourcePermissions: Record<string, Record<string, string[]>>
/** Approval capabilities */
canApprove: string[]
/** Task handling capabilities */
canHandle: string[]
/** Inheritance chain (for debugging) */
inheritanceChain: string[]
}
/**
* Resolve permissions for a position in the org hierarchy
*/
export function resolvePermissions(
org: Organization,
positionId: string
): ResolvedPermissions | null {
// Find the position
let position: Position | undefined
let team: Team | undefined
let department: Department | undefined
// Search through hierarchy
for (const dept of org.departments || []) {
for (const t of dept.teams || []) {
const pos = t.positions?.find((p) => p.id === positionId)
if (pos) {
position = pos
team = t
department = dept
break
}
}
if (position) break
}
// Also check standalone teams
if (!position) {
for (const t of org.teams || []) {
const pos = t.positions?.find((p) => p.id === positionId)
if (pos) {
position = pos
team = t
break
}
}
}
if (!position) return null
// Find the role
const role = org.roles?.find((r) => r['id'] === position.roleId)
// Build inheritance chain
const inheritanceChain: string[] = []
const permissions: Record<string, string[]> = {}
const resourcePermissions: Record<string, Record<string, string[]>> = {}
const canApprove: string[] = []
const canHandle: string[] = []
// 1. Department defaults
if (department?.defaultPermissions) {
inheritanceChain.push(`department:${department.id}`)
mergePermissions(permissions, department.defaultPermissions)
}
// 2. Team defaults
if (team?.defaultPermissions) {
inheritanceChain.push(`team:${team.id}`)
mergePermissions(permissions, team.defaultPermissions)
}
// 3. Team resources (scoped permissions)
if (team?.resources) {
for (const [resourceType, resourceIds] of Object.entries(team.resources)) {
if (resourceIds) {
for (const resourceId of resourceIds) {
const key = `${resourceType}:${resourceId}`
resourcePermissions[key] = resourcePermissions[key] || {}
mergePermissions(resourcePermissions[key], team.defaultPermissions || {})
}
}
}
}
// 4. Role permissions
if (role?.permissions) {
inheritanceChain.push(`role:${role['id']}`)
mergePermissions(permissions, role.permissions)
}
// 5. Role capabilities
if (role?.['canApprove']) {
canApprove.push(...role['canApprove'])
}
if (role?.['canHandle']) {
canHandle.push(...role['canHandle'])
}
// 6. Position-specific permissions
if (position.additionalPermissions) {
inheritanceChain.push(`position:${position.id}`)
mergePermissions(permissions, position.additionalPermissions)
// Handle resource-specific permissions
for (const [key, perms] of Object.entries(position.additionalPermissions)) {
if (key.includes(':')) {
resourcePermissions[key] = resourcePermissions[key] || {}
resourcePermissions[key] = { ...resourcePermissions[key], _direct: perms }
}
}
}
return {
workerId: position.workerId || '',
positionId: position.id,
permissions,
resourcePermissions,
canApprove: [...new Set(canApprove)],
canHandle: [...new Set(canHandle)],
inheritanceChain,
}
}
/**
* Merge permissions into target
*/
function mergePermissions(
target: Record<string, string[]>,
source: Record<string, string[]>
): void {
for (const [key, perms] of Object.entries(source)) {
if (!target[key]) {
target[key] = []
}
for (const perm of perms) {
if (!target[key].includes(perm)) {
target[key].push(perm)
}
}
}
}
/**
* Get approval chain for a request
*/
export function getApprovalChainForRequest(
org: Organization,
requestType: string,
amount?: number
): ApproverSpec[] {
const chain = org.approvalChains?.find((c) => c.type === requestType && c.active !== false)
if (!chain) return []
// Find the appropriate level based on amount
const levels = [...chain.levels].sort((a, b) => (a.threshold || 0) - (b.threshold || 0))
for (const level of levels.reverse()) {
if (amount === undefined || (level.threshold && amount <= level.threshold)) {
return level.approvers
}
}
// Return highest level if amount exceeds all thresholds
return levels[levels.length - 1]?.approvers || []
}
/**
* Find manager for a position (follows reportsTo chain)
*/
export function findManager(org: Organization, positionId: string): Position | null {
// Find the position
for (const dept of org.departments || []) {
for (const team of dept.teams || []) {
const position = team.positions?.find((p) => p.id === positionId)
if (position?.reportsTo) {
// Find the manager position
for (const d of org.departments || []) {
for (const t of d.teams || []) {
const manager = t.positions?.find((p) => p.id === position.reportsTo)
if (manager) return manager
}
}
}
}
}
return null
}