business-as-code 0.2.1 → 2.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +5 -0
- package/CHANGELOG.md +17 -0
- package/IMPLEMENTATION.md +226 -0
- package/README.md +1133 -193
- package/dist/business.d.ts +62 -0
- package/dist/business.d.ts.map +1 -0
- package/dist/business.js +109 -0
- package/dist/business.js.map +1 -0
- package/dist/dollar.d.ts +60 -0
- package/dist/dollar.d.ts.map +1 -0
- package/dist/dollar.js +107 -0
- package/dist/dollar.js.map +1 -0
- package/dist/entities/assets.d.ts +21 -0
- package/dist/entities/assets.d.ts.map +1 -0
- package/dist/entities/assets.js +323 -0
- package/dist/entities/assets.js.map +1 -0
- package/dist/entities/business.d.ts +36 -0
- package/dist/entities/business.d.ts.map +1 -0
- package/dist/entities/business.js +370 -0
- package/dist/entities/business.js.map +1 -0
- package/dist/entities/communication.d.ts +21 -0
- package/dist/entities/communication.d.ts.map +1 -0
- package/dist/entities/communication.js +255 -0
- package/dist/entities/communication.js.map +1 -0
- package/dist/entities/customers.d.ts +58 -0
- package/dist/entities/customers.d.ts.map +1 -0
- package/dist/entities/customers.js +989 -0
- package/dist/entities/customers.js.map +1 -0
- package/dist/entities/financials.d.ts +59 -0
- package/dist/entities/financials.d.ts.map +1 -0
- package/dist/entities/financials.js +932 -0
- package/dist/entities/financials.js.map +1 -0
- package/dist/entities/goals.d.ts +58 -0
- package/dist/entities/goals.d.ts.map +1 -0
- package/dist/entities/goals.js +800 -0
- package/dist/entities/goals.js.map +1 -0
- package/dist/entities/index.d.ts +299 -0
- package/dist/entities/index.d.ts.map +1 -0
- package/dist/entities/index.js +198 -0
- package/dist/entities/index.js.map +1 -0
- package/dist/entities/legal.d.ts +21 -0
- package/dist/entities/legal.d.ts.map +1 -0
- package/dist/entities/legal.js +301 -0
- package/dist/entities/legal.js.map +1 -0
- package/dist/entities/market.d.ts +21 -0
- package/dist/entities/market.d.ts.map +1 -0
- package/dist/entities/market.js +301 -0
- package/dist/entities/market.js.map +1 -0
- package/dist/entities/marketing.d.ts +67 -0
- package/dist/entities/marketing.d.ts.map +1 -0
- package/dist/entities/marketing.js +1157 -0
- package/dist/entities/marketing.js.map +1 -0
- package/dist/entities/offerings.d.ts +51 -0
- package/dist/entities/offerings.d.ts.map +1 -0
- package/dist/entities/offerings.js +727 -0
- package/dist/entities/offerings.js.map +1 -0
- package/dist/entities/operations.d.ts +58 -0
- package/dist/entities/operations.d.ts.map +1 -0
- package/dist/entities/operations.js +787 -0
- package/dist/entities/operations.js.map +1 -0
- package/dist/entities/organization.d.ts +57 -0
- package/dist/entities/organization.d.ts.map +1 -0
- package/dist/entities/organization.js +807 -0
- package/dist/entities/organization.js.map +1 -0
- package/dist/entities/partnerships.d.ts +21 -0
- package/dist/entities/partnerships.d.ts.map +1 -0
- package/dist/entities/partnerships.js +300 -0
- package/dist/entities/partnerships.js.map +1 -0
- package/dist/entities/planning.d.ts +87 -0
- package/dist/entities/planning.d.ts.map +1 -0
- package/dist/entities/planning.js +271 -0
- package/dist/entities/planning.js.map +1 -0
- package/dist/entities/projects.d.ts +25 -0
- package/dist/entities/projects.d.ts.map +1 -0
- package/dist/entities/projects.js +349 -0
- package/dist/entities/projects.js.map +1 -0
- package/dist/entities/risk.d.ts +21 -0
- package/dist/entities/risk.d.ts.map +1 -0
- package/dist/entities/risk.js +293 -0
- package/dist/entities/risk.js.map +1 -0
- package/dist/entities/sales.d.ts +72 -0
- package/dist/entities/sales.d.ts.map +1 -0
- package/dist/entities/sales.js +1248 -0
- package/dist/entities/sales.js.map +1 -0
- package/dist/financials.d.ts +130 -0
- package/dist/financials.d.ts.map +1 -0
- package/dist/financials.js +297 -0
- package/dist/financials.js.map +1 -0
- package/dist/goals.d.ts +87 -0
- package/dist/goals.d.ts.map +1 -0
- package/dist/goals.js +215 -0
- package/dist/goals.js.map +1 -0
- package/dist/index.d.ts +97 -4
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +131 -1079
- package/dist/index.js.map +1 -1
- package/dist/kpis.d.ts +118 -0
- package/dist/kpis.d.ts.map +1 -0
- package/dist/kpis.js +232 -0
- package/dist/kpis.js.map +1 -0
- package/dist/metrics.d.ts +448 -0
- package/dist/metrics.d.ts.map +1 -0
- package/dist/metrics.js +325 -0
- package/dist/metrics.js.map +1 -0
- package/dist/okrs.d.ts +123 -0
- package/dist/okrs.d.ts.map +1 -0
- package/dist/okrs.js +269 -0
- package/dist/okrs.js.map +1 -0
- package/dist/organization.d.ts +585 -0
- package/dist/organization.d.ts.map +1 -0
- package/dist/organization.js +173 -0
- package/dist/organization.js.map +1 -0
- package/dist/process.d.ts +112 -0
- package/dist/process.d.ts.map +1 -0
- package/dist/process.js +241 -0
- package/dist/process.js.map +1 -0
- package/dist/product.d.ts +85 -0
- package/dist/product.d.ts.map +1 -0
- package/dist/product.js +145 -0
- package/dist/product.js.map +1 -0
- package/dist/queries.d.ts +304 -0
- package/dist/queries.d.ts.map +1 -0
- package/dist/queries.js +415 -0
- package/dist/queries.js.map +1 -0
- package/dist/roles.d.ts +340 -0
- package/dist/roles.d.ts.map +1 -0
- package/dist/roles.js +255 -0
- package/dist/roles.js.map +1 -0
- package/dist/service.d.ts +61 -0
- package/dist/service.d.ts.map +1 -0
- package/dist/service.js +140 -0
- package/dist/service.js.map +1 -0
- package/dist/types.d.ts +459 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/dist/vision.d.ts +38 -0
- package/dist/vision.d.ts.map +1 -0
- package/dist/vision.js +68 -0
- package/dist/vision.js.map +1 -0
- package/dist/workflow.d.ts +115 -0
- package/dist/workflow.d.ts.map +1 -0
- package/dist/workflow.js +247 -0
- package/dist/workflow.js.map +1 -0
- package/examples/basic-usage.ts +307 -0
- package/package.json +19 -60
- package/src/business.ts +121 -0
- package/src/dollar.ts +132 -0
- package/src/entities/assets.ts +332 -0
- package/src/entities/business.ts +406 -0
- package/src/entities/communication.ts +264 -0
- package/src/entities/customers.ts +1072 -0
- package/src/entities/financials.ts +1011 -0
- package/src/entities/goals.ts +871 -0
- package/src/entities/index.ts +383 -0
- package/src/entities/legal.ts +310 -0
- package/src/entities/market.ts +310 -0
- package/src/entities/marketing.ts +1249 -0
- package/src/entities/offerings.ts +789 -0
- package/src/entities/operations.ts +861 -0
- package/src/entities/organization.ts +876 -0
- package/src/entities/partnerships.ts +309 -0
- package/src/entities/planning.ts +307 -0
- package/src/entities/projects.ts +360 -0
- package/src/entities/risk.ts +302 -0
- package/src/entities/sales.ts +1352 -0
- package/src/financials.ts +352 -0
- package/src/goals.ts +250 -0
- package/src/index.test.ts +336 -0
- package/src/index.ts +530 -0
- package/src/kpis.ts +275 -0
- package/src/metrics.ts +825 -0
- package/src/okrs.ts +325 -0
- package/src/organization.ts +909 -0
- package/src/process.ts +272 -0
- package/src/product.ts +178 -0
- package/src/queries.ts +767 -0
- package/src/roles.ts +686 -0
- package/src/service.ts +164 -0
- package/src/types.ts +493 -0
- package/src/vision.ts +88 -0
- package/src/workflow.ts +280 -0
- package/tsconfig.json +9 -0
- package/dist/loaders/index.d.ts +0 -174
- package/dist/loaders/index.js +0 -366
- package/dist/loaders/index.js.map +0 -1
- package/dist/schema/index.d.ts +0 -146
- package/dist/schema/index.js +0 -716
- package/dist/schema/index.js.map +0 -1
- package/dist/types-CJ9eGS_C.d.ts +0 -86
|
@@ -0,0 +1,909 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Organization Structure - Flows to FGA/RBAC
|
|
3
|
+
*
|
|
4
|
+
* Defines the complete organizational hierarchy:
|
|
5
|
+
*
|
|
6
|
+
* Organization
|
|
7
|
+
* └── Department
|
|
8
|
+
* └── Team
|
|
9
|
+
* └── Position (Role + Worker)
|
|
10
|
+
* └── Permissions (FGA/RBAC)
|
|
11
|
+
*
|
|
12
|
+
* This structure enables:
|
|
13
|
+
* - Hierarchical permission inheritance
|
|
14
|
+
* - Role-based task assignment
|
|
15
|
+
* - Approval chains based on org structure
|
|
16
|
+
* - Resource access control based on department/team
|
|
17
|
+
*
|
|
18
|
+
* @packageDocumentation
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import type { BusinessRole, TaskRoutingRule, WorkflowRole } from './roles.js'
|
|
22
|
+
|
|
23
|
+
// =============================================================================
|
|
24
|
+
// Organization Hierarchy
|
|
25
|
+
// =============================================================================
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Organization - top-level business entity
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```ts
|
|
32
|
+
* const acme: Organization = {
|
|
33
|
+
* id: 'org_acme',
|
|
34
|
+
* name: 'Acme Corp',
|
|
35
|
+
* domain: 'acme.com',
|
|
36
|
+
* industry: 'technology',
|
|
37
|
+
*
|
|
38
|
+
* // Hierarchy
|
|
39
|
+
* departments: [engineering, sales, support],
|
|
40
|
+
*
|
|
41
|
+
* // Global settings
|
|
42
|
+
* settings: {
|
|
43
|
+
* defaultCurrency: 'USD',
|
|
44
|
+
* timezone: 'America/Los_Angeles',
|
|
45
|
+
* workWeek: ['monday', 'tuesday', 'wednesday', 'thursday', 'friday'],
|
|
46
|
+
* },
|
|
47
|
+
*
|
|
48
|
+
* // Resource hierarchy for FGA
|
|
49
|
+
* resourceHierarchy: {
|
|
50
|
+
* organization: { children: ['department', 'project', 'repository'] },
|
|
51
|
+
* department: { parent: 'organization', children: ['team'] },
|
|
52
|
+
* team: { parent: 'department', children: ['position'] },
|
|
53
|
+
* },
|
|
54
|
+
* }
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
57
|
+
export interface Organization {
|
|
58
|
+
/** Unique identifier */
|
|
59
|
+
id: string
|
|
60
|
+
|
|
61
|
+
/** Organization name */
|
|
62
|
+
name: string
|
|
63
|
+
|
|
64
|
+
/** Primary domain */
|
|
65
|
+
domain?: string
|
|
66
|
+
|
|
67
|
+
/** Legal name (if different) */
|
|
68
|
+
legalName?: string
|
|
69
|
+
|
|
70
|
+
/** Industry/sector */
|
|
71
|
+
industry?: string
|
|
72
|
+
|
|
73
|
+
/** Mission statement */
|
|
74
|
+
mission?: string
|
|
75
|
+
|
|
76
|
+
/** Core values */
|
|
77
|
+
values?: string[]
|
|
78
|
+
|
|
79
|
+
/** Founded date */
|
|
80
|
+
foundedAt?: Date
|
|
81
|
+
|
|
82
|
+
/** Headquarters location */
|
|
83
|
+
headquarters?: Address
|
|
84
|
+
|
|
85
|
+
/** Organization settings */
|
|
86
|
+
settings?: OrganizationSettings
|
|
87
|
+
|
|
88
|
+
/** Departments */
|
|
89
|
+
departments?: Department[]
|
|
90
|
+
|
|
91
|
+
/** Standalone teams (not in departments) */
|
|
92
|
+
teams?: Team[]
|
|
93
|
+
|
|
94
|
+
/** Organization-wide roles */
|
|
95
|
+
roles?: BusinessRole[]
|
|
96
|
+
|
|
97
|
+
/** Resource hierarchy for FGA */
|
|
98
|
+
resourceHierarchy?: ResourceHierarchy
|
|
99
|
+
|
|
100
|
+
/** Global approval chains */
|
|
101
|
+
approvalChains?: ApprovalChain[]
|
|
102
|
+
|
|
103
|
+
/** Task routing rules */
|
|
104
|
+
routingRules?: TaskRoutingRule[]
|
|
105
|
+
|
|
106
|
+
/** Metadata */
|
|
107
|
+
metadata?: Record<string, unknown>
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Organization settings
|
|
112
|
+
*/
|
|
113
|
+
export interface OrganizationSettings {
|
|
114
|
+
/** Default currency */
|
|
115
|
+
defaultCurrency?: string
|
|
116
|
+
|
|
117
|
+
/** Default timezone */
|
|
118
|
+
timezone?: string
|
|
119
|
+
|
|
120
|
+
/** Work week days */
|
|
121
|
+
workWeek?: string[]
|
|
122
|
+
|
|
123
|
+
/** Business hours */
|
|
124
|
+
businessHours?: {
|
|
125
|
+
start: string
|
|
126
|
+
end: string
|
|
127
|
+
timezone?: string
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** Fiscal year start month (1-12) */
|
|
131
|
+
fiscalYearStart?: number
|
|
132
|
+
|
|
133
|
+
/** Default language */
|
|
134
|
+
language?: string
|
|
135
|
+
|
|
136
|
+
/** Date format */
|
|
137
|
+
dateFormat?: string
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Address
|
|
142
|
+
*/
|
|
143
|
+
export interface Address {
|
|
144
|
+
street?: string
|
|
145
|
+
city?: string
|
|
146
|
+
state?: string
|
|
147
|
+
postalCode?: string
|
|
148
|
+
country?: string
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// =============================================================================
|
|
152
|
+
// Department
|
|
153
|
+
// =============================================================================
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Department - major organizational division
|
|
157
|
+
*
|
|
158
|
+
* @example
|
|
159
|
+
* ```ts
|
|
160
|
+
* const engineering: Department = {
|
|
161
|
+
* id: 'dept_eng',
|
|
162
|
+
* name: 'Engineering',
|
|
163
|
+
* code: 'ENG',
|
|
164
|
+
*
|
|
165
|
+
* // Leadership
|
|
166
|
+
* head: { roleId: 'role_cto', positionId: 'pos_cto_jane' },
|
|
167
|
+
*
|
|
168
|
+
* // Teams
|
|
169
|
+
* teams: [platformTeam, productTeam, infraTeam],
|
|
170
|
+
*
|
|
171
|
+
* // Budget
|
|
172
|
+
* budget: {
|
|
173
|
+
* annual: 5000000,
|
|
174
|
+
* currency: 'USD',
|
|
175
|
+
* categories: {
|
|
176
|
+
* salaries: 3500000,
|
|
177
|
+
* tools: 200000,
|
|
178
|
+
* infrastructure: 800000,
|
|
179
|
+
* training: 100000,
|
|
180
|
+
* },
|
|
181
|
+
* },
|
|
182
|
+
*
|
|
183
|
+
* // FGA: Department-level permissions
|
|
184
|
+
* defaultPermissions: {
|
|
185
|
+
* repository: ['read'],
|
|
186
|
+
* project: ['read'],
|
|
187
|
+
* },
|
|
188
|
+
* }
|
|
189
|
+
* ```
|
|
190
|
+
*/
|
|
191
|
+
export interface Department {
|
|
192
|
+
/** Unique identifier */
|
|
193
|
+
id: string
|
|
194
|
+
|
|
195
|
+
/** Department name */
|
|
196
|
+
name: string
|
|
197
|
+
|
|
198
|
+
/** Short code (e.g., 'ENG', 'SALES') */
|
|
199
|
+
code?: string
|
|
200
|
+
|
|
201
|
+
/** Description */
|
|
202
|
+
description?: string
|
|
203
|
+
|
|
204
|
+
/** Department head position */
|
|
205
|
+
head?: PositionRef
|
|
206
|
+
|
|
207
|
+
/** Parent department (for sub-departments) */
|
|
208
|
+
parentId?: string
|
|
209
|
+
|
|
210
|
+
/** Teams within department */
|
|
211
|
+
teams?: Team[]
|
|
212
|
+
|
|
213
|
+
/** Department budget */
|
|
214
|
+
budget?: Budget
|
|
215
|
+
|
|
216
|
+
/** Cost center code */
|
|
217
|
+
costCenter?: string
|
|
218
|
+
|
|
219
|
+
/** Department goals */
|
|
220
|
+
goals?: string[]
|
|
221
|
+
|
|
222
|
+
/** Default permissions for department members */
|
|
223
|
+
defaultPermissions?: Record<string, string[]>
|
|
224
|
+
|
|
225
|
+
/** Department-specific roles */
|
|
226
|
+
roles?: BusinessRole[]
|
|
227
|
+
|
|
228
|
+
/** Department-specific routing rules */
|
|
229
|
+
routingRules?: TaskRoutingRule[]
|
|
230
|
+
|
|
231
|
+
/** Metadata */
|
|
232
|
+
metadata?: Record<string, unknown>
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// =============================================================================
|
|
236
|
+
// Team
|
|
237
|
+
// =============================================================================
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Team - working group within a department
|
|
241
|
+
*
|
|
242
|
+
* @example
|
|
243
|
+
* ```ts
|
|
244
|
+
* const platformTeam: Team = {
|
|
245
|
+
* id: 'team_platform',
|
|
246
|
+
* name: 'Platform Team',
|
|
247
|
+
* departmentId: 'dept_eng',
|
|
248
|
+
*
|
|
249
|
+
* // Leadership
|
|
250
|
+
* lead: { roleId: 'role_lead', positionId: 'pos_lead_bob' },
|
|
251
|
+
*
|
|
252
|
+
* // Members
|
|
253
|
+
* positions: [
|
|
254
|
+
* { id: 'pos_1', roleId: 'role_engineer', workerId: 'worker_alice' },
|
|
255
|
+
* { id: 'pos_2', roleId: 'role_engineer', workerId: 'worker_charlie' },
|
|
256
|
+
* { id: 'pos_3', roleId: 'role_engineer', workerId: null }, // Open position
|
|
257
|
+
* ],
|
|
258
|
+
*
|
|
259
|
+
* // Team resources (for FGA scoping)
|
|
260
|
+
* resources: {
|
|
261
|
+
* repositories: ['platform-core', 'platform-api'],
|
|
262
|
+
* projects: ['platform-v2'],
|
|
263
|
+
* },
|
|
264
|
+
*
|
|
265
|
+
* // Team-level permissions (inherited by members)
|
|
266
|
+
* defaultPermissions: {
|
|
267
|
+
* repository: ['read', 'edit', 'act:merge'],
|
|
268
|
+
* project: ['read', 'edit'],
|
|
269
|
+
* },
|
|
270
|
+
* }
|
|
271
|
+
* ```
|
|
272
|
+
*/
|
|
273
|
+
export interface Team {
|
|
274
|
+
/** Unique identifier */
|
|
275
|
+
id: string
|
|
276
|
+
|
|
277
|
+
/** Team name */
|
|
278
|
+
name: string
|
|
279
|
+
|
|
280
|
+
/** Parent department */
|
|
281
|
+
departmentId?: string
|
|
282
|
+
|
|
283
|
+
/** Description */
|
|
284
|
+
description?: string
|
|
285
|
+
|
|
286
|
+
/** Team lead position */
|
|
287
|
+
lead?: PositionRef
|
|
288
|
+
|
|
289
|
+
/** Team positions */
|
|
290
|
+
positions?: Position[]
|
|
291
|
+
|
|
292
|
+
/** Team objectives */
|
|
293
|
+
objectives?: string[]
|
|
294
|
+
|
|
295
|
+
/** Team resources (for FGA scoping) */
|
|
296
|
+
resources?: TeamResources
|
|
297
|
+
|
|
298
|
+
/** Team budget (if separate from department) */
|
|
299
|
+
budget?: Budget
|
|
300
|
+
|
|
301
|
+
/** Default permissions for team members */
|
|
302
|
+
defaultPermissions?: Record<string, string[]>
|
|
303
|
+
|
|
304
|
+
/** Workflow roles for this team */
|
|
305
|
+
workflowRoles?: WorkflowRole[]
|
|
306
|
+
|
|
307
|
+
/** Communication channels */
|
|
308
|
+
channels?: TeamChannels
|
|
309
|
+
|
|
310
|
+
/** Metadata */
|
|
311
|
+
metadata?: Record<string, unknown>
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Team resources - scopes FGA permissions
|
|
316
|
+
*/
|
|
317
|
+
export interface TeamResources {
|
|
318
|
+
/** Owned repositories */
|
|
319
|
+
repositories?: string[]
|
|
320
|
+
|
|
321
|
+
/** Owned projects */
|
|
322
|
+
projects?: string[]
|
|
323
|
+
|
|
324
|
+
/** Owned products */
|
|
325
|
+
products?: string[]
|
|
326
|
+
|
|
327
|
+
/** Custom resource types */
|
|
328
|
+
[resourceType: string]: string[] | undefined
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Team communication channels
|
|
333
|
+
*/
|
|
334
|
+
export interface TeamChannels {
|
|
335
|
+
slack?: string
|
|
336
|
+
teams?: string
|
|
337
|
+
email?: string
|
|
338
|
+
discord?: string
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// =============================================================================
|
|
342
|
+
// Position - Role + Worker Assignment
|
|
343
|
+
// =============================================================================
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Position - a role filled by a worker
|
|
347
|
+
*
|
|
348
|
+
* The position is the link between:
|
|
349
|
+
* - Business Role (responsibilities, permissions)
|
|
350
|
+
* - Worker (human or AI agent)
|
|
351
|
+
* - Location in org hierarchy (team → department → org)
|
|
352
|
+
*
|
|
353
|
+
* @example
|
|
354
|
+
* ```ts
|
|
355
|
+
* const seniorEngineerPosition: Position = {
|
|
356
|
+
* id: 'pos_se_123',
|
|
357
|
+
* title: 'Senior Software Engineer',
|
|
358
|
+
*
|
|
359
|
+
* // Role defines what this position can do
|
|
360
|
+
* roleId: 'role_senior_engineer',
|
|
361
|
+
*
|
|
362
|
+
* // Worker currently filling this position
|
|
363
|
+
* workerId: 'worker_alice',
|
|
364
|
+
* workerType: 'human',
|
|
365
|
+
*
|
|
366
|
+
* // Hierarchy
|
|
367
|
+
* teamId: 'team_platform',
|
|
368
|
+
* reportsTo: 'pos_lead_bob',
|
|
369
|
+
*
|
|
370
|
+
* // Position-specific permissions (added to role permissions)
|
|
371
|
+
* additionalPermissions: {
|
|
372
|
+
* 'repository:platform-core': ['manage'],
|
|
373
|
+
* },
|
|
374
|
+
*
|
|
375
|
+
* // Employment details
|
|
376
|
+
* startDate: new Date('2023-01-15'),
|
|
377
|
+
* status: 'active',
|
|
378
|
+
* }
|
|
379
|
+
* ```
|
|
380
|
+
*/
|
|
381
|
+
export interface Position {
|
|
382
|
+
/** Unique identifier */
|
|
383
|
+
id: string
|
|
384
|
+
|
|
385
|
+
/** Position title */
|
|
386
|
+
title: string
|
|
387
|
+
|
|
388
|
+
/** Business role this position requires */
|
|
389
|
+
roleId: string
|
|
390
|
+
|
|
391
|
+
/** Worker filling this position (null = open position) */
|
|
392
|
+
workerId?: string | null
|
|
393
|
+
|
|
394
|
+
/** Type of worker (human, agent, or either) */
|
|
395
|
+
workerType?: 'human' | 'agent' | 'any'
|
|
396
|
+
|
|
397
|
+
/** Team this position belongs to */
|
|
398
|
+
teamId?: string
|
|
399
|
+
|
|
400
|
+
/** Position this reports to */
|
|
401
|
+
reportsTo?: string
|
|
402
|
+
|
|
403
|
+
/** Direct reports (position IDs) */
|
|
404
|
+
directReports?: string[]
|
|
405
|
+
|
|
406
|
+
/** Position-specific additional permissions */
|
|
407
|
+
additionalPermissions?: Record<string, string[]>
|
|
408
|
+
|
|
409
|
+
/** Start date */
|
|
410
|
+
startDate?: Date
|
|
411
|
+
|
|
412
|
+
/** End date (for contractors/temporary) */
|
|
413
|
+
endDate?: Date
|
|
414
|
+
|
|
415
|
+
/** Position status */
|
|
416
|
+
status?: 'active' | 'open' | 'on-leave' | 'terminated'
|
|
417
|
+
|
|
418
|
+
/** Full-time equivalent (1.0 = full-time) */
|
|
419
|
+
fte?: number
|
|
420
|
+
|
|
421
|
+
/** Location */
|
|
422
|
+
location?: string
|
|
423
|
+
|
|
424
|
+
/** Remote/hybrid/onsite */
|
|
425
|
+
workModel?: 'remote' | 'hybrid' | 'onsite'
|
|
426
|
+
|
|
427
|
+
/** Compensation */
|
|
428
|
+
compensation?: Compensation
|
|
429
|
+
|
|
430
|
+
/** Metadata */
|
|
431
|
+
metadata?: Record<string, unknown>
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Position reference
|
|
436
|
+
*/
|
|
437
|
+
export interface PositionRef {
|
|
438
|
+
positionId?: string
|
|
439
|
+
roleId?: string
|
|
440
|
+
workerId?: string
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* Compensation details
|
|
445
|
+
*/
|
|
446
|
+
export interface Compensation {
|
|
447
|
+
/** Base salary */
|
|
448
|
+
baseSalary?: number
|
|
449
|
+
|
|
450
|
+
/** Currency */
|
|
451
|
+
currency?: string
|
|
452
|
+
|
|
453
|
+
/** Pay frequency */
|
|
454
|
+
frequency?: 'hourly' | 'weekly' | 'biweekly' | 'monthly' | 'annual'
|
|
455
|
+
|
|
456
|
+
/** Bonus target percentage */
|
|
457
|
+
bonusTarget?: number
|
|
458
|
+
|
|
459
|
+
/** Equity grants */
|
|
460
|
+
equity?: {
|
|
461
|
+
type: 'options' | 'rsu' | 'shares'
|
|
462
|
+
amount: number
|
|
463
|
+
vestingSchedule?: string
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/** Band/level */
|
|
467
|
+
band?: string
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// =============================================================================
|
|
471
|
+
// Budget
|
|
472
|
+
// =============================================================================
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* Budget allocation
|
|
476
|
+
*/
|
|
477
|
+
export interface Budget {
|
|
478
|
+
/** Total annual budget */
|
|
479
|
+
annual?: number
|
|
480
|
+
|
|
481
|
+
/** Currency */
|
|
482
|
+
currency?: string
|
|
483
|
+
|
|
484
|
+
/** Budget period */
|
|
485
|
+
period?: string
|
|
486
|
+
|
|
487
|
+
/** Budget categories */
|
|
488
|
+
categories?: Record<string, number>
|
|
489
|
+
|
|
490
|
+
/** Spent to date */
|
|
491
|
+
spent?: number
|
|
492
|
+
|
|
493
|
+
/** Remaining */
|
|
494
|
+
remaining?: number
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// =============================================================================
|
|
498
|
+
// Resource Hierarchy - FGA Integration
|
|
499
|
+
// =============================================================================
|
|
500
|
+
|
|
501
|
+
/**
|
|
502
|
+
* Resource hierarchy definition for FGA
|
|
503
|
+
*
|
|
504
|
+
* Defines how resources relate to each other for permission inheritance.
|
|
505
|
+
*
|
|
506
|
+
* @example
|
|
507
|
+
* ```ts
|
|
508
|
+
* const hierarchy: ResourceHierarchy = {
|
|
509
|
+
* // Organization is root
|
|
510
|
+
* organization: {
|
|
511
|
+
* children: ['department', 'project', 'repository'],
|
|
512
|
+
* },
|
|
513
|
+
*
|
|
514
|
+
* // Department inherits from org
|
|
515
|
+
* department: {
|
|
516
|
+
* parent: 'organization',
|
|
517
|
+
* children: ['team'],
|
|
518
|
+
* },
|
|
519
|
+
*
|
|
520
|
+
* // Team inherits from department
|
|
521
|
+
* team: {
|
|
522
|
+
* parent: 'department',
|
|
523
|
+
* children: ['position'],
|
|
524
|
+
* },
|
|
525
|
+
*
|
|
526
|
+
* // Project can be org-level or team-level
|
|
527
|
+
* project: {
|
|
528
|
+
* parent: 'organization',
|
|
529
|
+
* alternateParents: ['team'],
|
|
530
|
+
* children: ['document', 'task'],
|
|
531
|
+
* },
|
|
532
|
+
*
|
|
533
|
+
* // Repository can be org-level or team-level
|
|
534
|
+
* repository: {
|
|
535
|
+
* parent: 'organization',
|
|
536
|
+
* alternateParents: ['team'],
|
|
537
|
+
* },
|
|
538
|
+
* }
|
|
539
|
+
* ```
|
|
540
|
+
*/
|
|
541
|
+
export interface ResourceHierarchy {
|
|
542
|
+
[resourceType: string]: ResourceHierarchyNode
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
/**
|
|
546
|
+
* Node in resource hierarchy
|
|
547
|
+
*/
|
|
548
|
+
export interface ResourceHierarchyNode {
|
|
549
|
+
/** Primary parent resource type */
|
|
550
|
+
parent?: string
|
|
551
|
+
|
|
552
|
+
/** Alternative parent types */
|
|
553
|
+
alternateParents?: string[]
|
|
554
|
+
|
|
555
|
+
/** Child resource types */
|
|
556
|
+
children?: string[]
|
|
557
|
+
|
|
558
|
+
/** Whether permissions cascade down */
|
|
559
|
+
inheritPermissions?: boolean
|
|
560
|
+
|
|
561
|
+
/** Maximum depth (for nested resources) */
|
|
562
|
+
maxDepth?: number
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
// =============================================================================
|
|
566
|
+
// Approval Chains
|
|
567
|
+
// =============================================================================
|
|
568
|
+
|
|
569
|
+
/**
|
|
570
|
+
* Approval chain - defines who approves what
|
|
571
|
+
*
|
|
572
|
+
* @example
|
|
573
|
+
* ```ts
|
|
574
|
+
* const expenseApprovalChain: ApprovalChain = {
|
|
575
|
+
* id: 'chain_expense',
|
|
576
|
+
* name: 'Expense Approval',
|
|
577
|
+
* type: 'expense',
|
|
578
|
+
*
|
|
579
|
+
* levels: [
|
|
580
|
+
* {
|
|
581
|
+
* threshold: 1000,
|
|
582
|
+
* approvers: [{ type: 'direct-manager' }],
|
|
583
|
+
* },
|
|
584
|
+
* {
|
|
585
|
+
* threshold: 5000,
|
|
586
|
+
* approvers: [
|
|
587
|
+
* { type: 'direct-manager' },
|
|
588
|
+
* { type: 'role', roleId: 'director' },
|
|
589
|
+
* ],
|
|
590
|
+
* },
|
|
591
|
+
* {
|
|
592
|
+
* threshold: 25000,
|
|
593
|
+
* approvers: [
|
|
594
|
+
* { type: 'direct-manager' },
|
|
595
|
+
* { type: 'role', roleId: 'vp' },
|
|
596
|
+
* { type: 'role', roleId: 'cfo' },
|
|
597
|
+
* ],
|
|
598
|
+
* },
|
|
599
|
+
* ],
|
|
600
|
+
*
|
|
601
|
+
* // Escalation settings
|
|
602
|
+
* escalation: {
|
|
603
|
+
* afterHours: 24,
|
|
604
|
+
* escalateTo: 'skip-level-manager',
|
|
605
|
+
* },
|
|
606
|
+
* }
|
|
607
|
+
* ```
|
|
608
|
+
*/
|
|
609
|
+
export interface ApprovalChain {
|
|
610
|
+
/** Chain identifier */
|
|
611
|
+
id: string
|
|
612
|
+
|
|
613
|
+
/** Chain name */
|
|
614
|
+
name: string
|
|
615
|
+
|
|
616
|
+
/** What type of requests this chain handles */
|
|
617
|
+
type: string
|
|
618
|
+
|
|
619
|
+
/** Approval levels */
|
|
620
|
+
levels: ApprovalLevel[]
|
|
621
|
+
|
|
622
|
+
/** Escalation rules */
|
|
623
|
+
escalation?: EscalationRule
|
|
624
|
+
|
|
625
|
+
/** Active/inactive */
|
|
626
|
+
active?: boolean
|
|
627
|
+
|
|
628
|
+
/** Metadata */
|
|
629
|
+
metadata?: Record<string, unknown>
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
/**
|
|
633
|
+
* Approval level within a chain
|
|
634
|
+
*/
|
|
635
|
+
export interface ApprovalLevel {
|
|
636
|
+
/** Threshold amount (requests above this need this level) */
|
|
637
|
+
threshold?: number
|
|
638
|
+
|
|
639
|
+
/** Approvers at this level */
|
|
640
|
+
approvers: ApproverSpec[]
|
|
641
|
+
|
|
642
|
+
/** How many approvers needed */
|
|
643
|
+
requiredApprovals?: number
|
|
644
|
+
|
|
645
|
+
/** Whether approvals at this level are sequential or parallel */
|
|
646
|
+
approvalMode?: 'sequential' | 'parallel' | 'any'
|
|
647
|
+
|
|
648
|
+
/** SLA for this level (hours) */
|
|
649
|
+
slaHours?: number
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
/**
|
|
653
|
+
* Approver specification
|
|
654
|
+
*/
|
|
655
|
+
export interface ApproverSpec {
|
|
656
|
+
/** Approver type */
|
|
657
|
+
type: 'direct-manager' | 'skip-level-manager' | 'role' | 'position' | 'worker' | 'team'
|
|
658
|
+
|
|
659
|
+
/** Role ID (if type is 'role') */
|
|
660
|
+
roleId?: string
|
|
661
|
+
|
|
662
|
+
/** Position ID (if type is 'position') */
|
|
663
|
+
positionId?: string
|
|
664
|
+
|
|
665
|
+
/** Worker ID (if type is 'worker') */
|
|
666
|
+
workerId?: string
|
|
667
|
+
|
|
668
|
+
/** Team ID (if type is 'team') */
|
|
669
|
+
teamId?: string
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
/**
|
|
673
|
+
* Escalation rule
|
|
674
|
+
*/
|
|
675
|
+
export interface EscalationRule {
|
|
676
|
+
/** Hours before escalation */
|
|
677
|
+
afterHours: number
|
|
678
|
+
|
|
679
|
+
/** Who to escalate to */
|
|
680
|
+
escalateTo: 'skip-level-manager' | 'department-head' | 'role' | 'position'
|
|
681
|
+
|
|
682
|
+
/** Role ID if escalating to role */
|
|
683
|
+
roleId?: string
|
|
684
|
+
|
|
685
|
+
/** Position ID if escalating to position */
|
|
686
|
+
positionId?: string
|
|
687
|
+
|
|
688
|
+
/** Maximum escalations */
|
|
689
|
+
maxEscalations?: number
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
// =============================================================================
|
|
693
|
+
// Permission Resolution
|
|
694
|
+
// =============================================================================
|
|
695
|
+
|
|
696
|
+
/**
|
|
697
|
+
* Resolved permissions for a worker
|
|
698
|
+
*
|
|
699
|
+
* Combines:
|
|
700
|
+
* - Organization-level defaults
|
|
701
|
+
* - Department-level permissions
|
|
702
|
+
* - Team-level permissions
|
|
703
|
+
* - Role permissions
|
|
704
|
+
* - Position-specific permissions
|
|
705
|
+
*/
|
|
706
|
+
export interface ResolvedPermissions {
|
|
707
|
+
/** Worker ID */
|
|
708
|
+
workerId: string
|
|
709
|
+
|
|
710
|
+
/** Position ID */
|
|
711
|
+
positionId: string
|
|
712
|
+
|
|
713
|
+
/** Effective permissions by resource type */
|
|
714
|
+
permissions: Record<string, string[]>
|
|
715
|
+
|
|
716
|
+
/** Resource-specific permissions */
|
|
717
|
+
resourcePermissions: Record<string, Record<string, string[]>>
|
|
718
|
+
|
|
719
|
+
/** Approval capabilities */
|
|
720
|
+
canApprove: string[]
|
|
721
|
+
|
|
722
|
+
/** Task handling capabilities */
|
|
723
|
+
canHandle: string[]
|
|
724
|
+
|
|
725
|
+
/** Inheritance chain (for debugging) */
|
|
726
|
+
inheritanceChain: string[]
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
/**
|
|
730
|
+
* Resolve permissions for a position in the org hierarchy
|
|
731
|
+
*/
|
|
732
|
+
export function resolvePermissions(
|
|
733
|
+
org: Organization,
|
|
734
|
+
positionId: string
|
|
735
|
+
): ResolvedPermissions | null {
|
|
736
|
+
// Find the position
|
|
737
|
+
let position: Position | undefined
|
|
738
|
+
let team: Team | undefined
|
|
739
|
+
let department: Department | undefined
|
|
740
|
+
|
|
741
|
+
// Search through hierarchy
|
|
742
|
+
for (const dept of org.departments || []) {
|
|
743
|
+
for (const t of dept.teams || []) {
|
|
744
|
+
const pos = t.positions?.find(p => p.id === positionId)
|
|
745
|
+
if (pos) {
|
|
746
|
+
position = pos
|
|
747
|
+
team = t
|
|
748
|
+
department = dept
|
|
749
|
+
break
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
if (position) break
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
// Also check standalone teams
|
|
756
|
+
if (!position) {
|
|
757
|
+
for (const t of org.teams || []) {
|
|
758
|
+
const pos = t.positions?.find(p => p.id === positionId)
|
|
759
|
+
if (pos) {
|
|
760
|
+
position = pos
|
|
761
|
+
team = t
|
|
762
|
+
break
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
if (!position) return null
|
|
768
|
+
|
|
769
|
+
// Find the role
|
|
770
|
+
const role = org.roles?.find(r => r.id === position.roleId)
|
|
771
|
+
|
|
772
|
+
// Build inheritance chain
|
|
773
|
+
const inheritanceChain: string[] = []
|
|
774
|
+
const permissions: Record<string, string[]> = {}
|
|
775
|
+
const resourcePermissions: Record<string, Record<string, string[]>> = {}
|
|
776
|
+
const canApprove: string[] = []
|
|
777
|
+
const canHandle: string[] = []
|
|
778
|
+
|
|
779
|
+
// 1. Department defaults
|
|
780
|
+
if (department?.defaultPermissions) {
|
|
781
|
+
inheritanceChain.push(`department:${department.id}`)
|
|
782
|
+
mergePermissions(permissions, department.defaultPermissions)
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
// 2. Team defaults
|
|
786
|
+
if (team?.defaultPermissions) {
|
|
787
|
+
inheritanceChain.push(`team:${team.id}`)
|
|
788
|
+
mergePermissions(permissions, team.defaultPermissions)
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
// 3. Team resources (scoped permissions)
|
|
792
|
+
if (team?.resources) {
|
|
793
|
+
for (const [resourceType, resourceIds] of Object.entries(team.resources)) {
|
|
794
|
+
if (resourceIds) {
|
|
795
|
+
for (const resourceId of resourceIds) {
|
|
796
|
+
const key = `${resourceType}:${resourceId}`
|
|
797
|
+
resourcePermissions[key] = resourcePermissions[key] || {}
|
|
798
|
+
mergePermissions(resourcePermissions[key], team.defaultPermissions || {})
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
// 4. Role permissions
|
|
805
|
+
if (role?.permissions) {
|
|
806
|
+
inheritanceChain.push(`role:${role.id}`)
|
|
807
|
+
mergePermissions(permissions, role.permissions)
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
// 5. Role capabilities
|
|
811
|
+
if (role?.canApprove) {
|
|
812
|
+
canApprove.push(...role.canApprove)
|
|
813
|
+
}
|
|
814
|
+
if (role?.canHandle) {
|
|
815
|
+
canHandle.push(...role.canHandle)
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
// 6. Position-specific permissions
|
|
819
|
+
if (position.additionalPermissions) {
|
|
820
|
+
inheritanceChain.push(`position:${position.id}`)
|
|
821
|
+
mergePermissions(permissions, position.additionalPermissions)
|
|
822
|
+
|
|
823
|
+
// Handle resource-specific permissions
|
|
824
|
+
for (const [key, perms] of Object.entries(position.additionalPermissions)) {
|
|
825
|
+
if (key.includes(':')) {
|
|
826
|
+
resourcePermissions[key] = resourcePermissions[key] || {}
|
|
827
|
+
resourcePermissions[key] = { ...resourcePermissions[key], _direct: perms }
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
return {
|
|
833
|
+
workerId: position.workerId || '',
|
|
834
|
+
positionId: position.id,
|
|
835
|
+
permissions,
|
|
836
|
+
resourcePermissions,
|
|
837
|
+
canApprove: [...new Set(canApprove)],
|
|
838
|
+
canHandle: [...new Set(canHandle)],
|
|
839
|
+
inheritanceChain,
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
/**
|
|
844
|
+
* Merge permissions into target
|
|
845
|
+
*/
|
|
846
|
+
function mergePermissions(
|
|
847
|
+
target: Record<string, string[]>,
|
|
848
|
+
source: Record<string, string[]>
|
|
849
|
+
): void {
|
|
850
|
+
for (const [key, perms] of Object.entries(source)) {
|
|
851
|
+
if (!target[key]) {
|
|
852
|
+
target[key] = []
|
|
853
|
+
}
|
|
854
|
+
for (const perm of perms) {
|
|
855
|
+
if (!target[key].includes(perm)) {
|
|
856
|
+
target[key].push(perm)
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
/**
|
|
863
|
+
* Get approval chain for a request
|
|
864
|
+
*/
|
|
865
|
+
export function getApprovalChainForRequest(
|
|
866
|
+
org: Organization,
|
|
867
|
+
requestType: string,
|
|
868
|
+
amount?: number
|
|
869
|
+
): ApproverSpec[] {
|
|
870
|
+
const chain = org.approvalChains?.find(c => c.type === requestType && c.active !== false)
|
|
871
|
+
if (!chain) return []
|
|
872
|
+
|
|
873
|
+
// Find the appropriate level based on amount
|
|
874
|
+
const levels = [...chain.levels].sort((a, b) => (a.threshold || 0) - (b.threshold || 0))
|
|
875
|
+
|
|
876
|
+
for (const level of levels.reverse()) {
|
|
877
|
+
if (amount === undefined || (level.threshold && amount <= level.threshold)) {
|
|
878
|
+
return level.approvers
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
// Return highest level if amount exceeds all thresholds
|
|
883
|
+
return levels[levels.length - 1]?.approvers || []
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
/**
|
|
887
|
+
* Find manager for a position (follows reportsTo chain)
|
|
888
|
+
*/
|
|
889
|
+
export function findManager(
|
|
890
|
+
org: Organization,
|
|
891
|
+
positionId: string
|
|
892
|
+
): Position | null {
|
|
893
|
+
// Find the position
|
|
894
|
+
for (const dept of org.departments || []) {
|
|
895
|
+
for (const team of dept.teams || []) {
|
|
896
|
+
const position = team.positions?.find(p => p.id === positionId)
|
|
897
|
+
if (position?.reportsTo) {
|
|
898
|
+
// Find the manager position
|
|
899
|
+
for (const d of org.departments || []) {
|
|
900
|
+
for (const t of d.teams || []) {
|
|
901
|
+
const manager = t.positions?.find(p => p.id === position.reportsTo)
|
|
902
|
+
if (manager) return manager
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
return null
|
|
909
|
+
}
|