@elevasis/core 0.21.0 → 0.22.0

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.
Files changed (59) hide show
  1. package/dist/index.d.ts +416 -6
  2. package/dist/index.js +240 -15
  3. package/dist/knowledge/index.d.ts +97 -1
  4. package/dist/organization-model/index.d.ts +416 -6
  5. package/dist/organization-model/index.js +240 -15
  6. package/dist/test-utils/index.d.ts +216 -1
  7. package/dist/test-utils/index.js +230 -14
  8. package/package.json +3 -3
  9. package/src/_gen/__tests__/__snapshots__/contracts.md.snap +495 -302
  10. package/src/auth/multi-tenancy/permissions.ts +20 -8
  11. package/src/business/README.md +2 -2
  12. package/src/business/acquisition/api-schemas.test.ts +173 -0
  13. package/src/business/acquisition/api-schemas.ts +125 -7
  14. package/src/business/acquisition/index.ts +12 -0
  15. package/src/business/clients/api-schemas.test.ts +115 -0
  16. package/src/business/clients/api-schemas.ts +158 -0
  17. package/src/business/clients/index.ts +1 -0
  18. package/src/business/deals/api-schemas.ts +8 -0
  19. package/src/business/index.ts +5 -2
  20. package/src/business/projects/types.ts +19 -0
  21. package/src/execution/engine/__tests__/fixtures/test-agents.ts +10 -8
  22. package/src/execution/engine/agent/core/__tests__/agent.test.ts +16 -12
  23. package/src/execution/engine/agent/core/__tests__/error-passthrough.test.ts +4 -3
  24. package/src/execution/engine/agent/core/types.ts +25 -15
  25. package/src/execution/engine/agent/index.ts +6 -4
  26. package/src/execution/engine/agent/reasoning/__tests__/request-builder.test.ts +24 -18
  27. package/src/execution/engine/index.ts +3 -0
  28. package/src/execution/engine/workflow/types.ts +7 -0
  29. package/src/organization-model/README.md +10 -3
  30. package/src/organization-model/__tests__/defaults.test.ts +6 -0
  31. package/src/organization-model/__tests__/domains/resources.test.ts +188 -0
  32. package/src/organization-model/__tests__/domains/roles.test.ts +402 -347
  33. package/src/organization-model/__tests__/domains/systems.test.ts +193 -0
  34. package/src/organization-model/__tests__/knowledge.test.ts +39 -0
  35. package/src/organization-model/__tests__/resolve.test.ts +1 -1
  36. package/src/organization-model/defaults.ts +24 -3
  37. package/src/organization-model/domains/knowledge.ts +3 -2
  38. package/src/organization-model/domains/resources.ts +88 -0
  39. package/src/organization-model/domains/roles.ts +93 -55
  40. package/src/organization-model/domains/systems.ts +46 -0
  41. package/src/organization-model/icons.ts +1 -0
  42. package/src/organization-model/index.ts +2 -0
  43. package/src/organization-model/organization-model.mdx +33 -14
  44. package/src/organization-model/published.ts +52 -1
  45. package/src/organization-model/schema.ts +121 -0
  46. package/src/organization-model/types.ts +46 -1
  47. package/src/platform/api/types.ts +38 -35
  48. package/src/platform/registry/__tests__/resource-registry.test.ts +2051 -2005
  49. package/src/platform/registry/__tests__/validation.test.ts +1343 -1086
  50. package/src/platform/registry/index.ts +14 -0
  51. package/src/platform/registry/resource-registry.ts +40 -2
  52. package/src/platform/registry/serialization.ts +241 -202
  53. package/src/platform/registry/serialized-types.ts +1 -0
  54. package/src/platform/registry/types.ts +411 -361
  55. package/src/platform/registry/validation.ts +743 -513
  56. package/src/projects/api-schemas.ts +290 -267
  57. package/src/reference/_generated/contracts.md +495 -302
  58. package/src/reference/glossary.md +8 -3
  59. package/src/supabase/database.types.ts +121 -0
@@ -0,0 +1,193 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import {
3
+ DEFAULT_ORGANIZATION_MODEL_SYSTEMS,
4
+ SystemEntrySchema,
5
+ SystemKindSchema,
6
+ SystemsDomainSchema,
7
+ SystemStatusSchema
8
+ } from '../../domains/systems'
9
+ import { resolveOrganizationModel } from '../../resolve'
10
+
11
+ const VALID_SYSTEM = {
12
+ id: 'sys.lead-gen',
13
+ title: 'Lead Generation Pipeline',
14
+ description: 'Coordinates prospecting, enrichment, qualification, and outreach preparation.',
15
+ kind: 'operational' as const,
16
+ status: 'active' as const
17
+ }
18
+
19
+ describe('SystemEntrySchema - positive parse', () => {
20
+ it('accepts a minimal tenant-defined system', () => {
21
+ const result = SystemEntrySchema.safeParse(VALID_SYSTEM)
22
+
23
+ expect(result.success).toBe(true)
24
+ if (result.success) {
25
+ expect(result.data.governedByKnowledge).toEqual([])
26
+ expect(result.data.drivesGoals).toEqual([])
27
+ }
28
+ })
29
+
30
+ it('accepts all optional governance references when present', () => {
31
+ const result = SystemEntrySchema.safeParse({
32
+ ...VALID_SYSTEM,
33
+ responsibleRoleId: 'role.sales-ops',
34
+ governedByKnowledge: ['knowledge.lead-gen-playbook'],
35
+ drivesGoals: ['goal.pipeline-coverage']
36
+ })
37
+
38
+ expect(result.success).toBe(true)
39
+ })
40
+
41
+ it('trims id, title, and description', () => {
42
+ const result = SystemEntrySchema.safeParse({
43
+ ...VALID_SYSTEM,
44
+ id: ' sys.crm ',
45
+ title: ' CRM ',
46
+ description: ' Owns relationship and deal pipeline operations. '
47
+ })
48
+
49
+ expect(result.success).toBe(true)
50
+ if (result.success) {
51
+ expect(result.data.id).toBe('sys.crm')
52
+ expect(result.data.title).toBe('CRM')
53
+ expect(result.data.description).toBe('Owns relationship and deal pipeline operations.')
54
+ }
55
+ })
56
+ })
57
+
58
+ describe('SystemEntrySchema - negative parse', () => {
59
+ it('rejects missing required fields', () => {
60
+ expect(SystemEntrySchema.safeParse({ id: 'sys.missing' }).success).toBe(false)
61
+ })
62
+
63
+ it('rejects an unknown system kind', () => {
64
+ expect(SystemEntrySchema.safeParse({ ...VALID_SYSTEM, kind: 'sales' }).success).toBe(false)
65
+ })
66
+
67
+ it('rejects an unknown system status', () => {
68
+ expect(SystemEntrySchema.safeParse({ ...VALID_SYSTEM, status: 'paused' }).success).toBe(false)
69
+ })
70
+
71
+ it('rejects IDs that do not match the OM model-id format', () => {
72
+ expect(SystemEntrySchema.safeParse({ ...VALID_SYSTEM, id: 'Sys Lead Gen' }).success).toBe(false)
73
+ })
74
+ })
75
+
76
+ describe('SystemsDomainSchema', () => {
77
+ it('accepts an empty systems array', () => {
78
+ expect(SystemsDomainSchema.safeParse({ systems: [] }).success).toBe(true)
79
+ })
80
+
81
+ it('defaults systems to an empty array when omitted', () => {
82
+ const result = SystemsDomainSchema.safeParse({})
83
+
84
+ expect(result.success).toBe(true)
85
+ if (result.success) {
86
+ expect(result.data).toEqual(DEFAULT_ORGANIZATION_MODEL_SYSTEMS)
87
+ }
88
+ })
89
+ })
90
+
91
+ describe('SystemKindSchema and SystemStatusSchema', () => {
92
+ it.each(['product', 'operational', 'platform', 'diagnostic'] as const)('accepts kind "%s"', (kind) => {
93
+ expect(SystemKindSchema.safeParse(kind).success).toBe(true)
94
+ })
95
+
96
+ it.each(['active', 'deprecated', 'archived'] as const)('accepts status "%s"', (status) => {
97
+ expect(SystemStatusSchema.safeParse(status).success).toBe(true)
98
+ })
99
+ })
100
+
101
+ describe('resolveOrganizationModel - systems domain integration', () => {
102
+ it('omitting systems resolves to the empty default catalog', () => {
103
+ const model = resolveOrganizationModel({})
104
+
105
+ expect(model.systems).toEqual(DEFAULT_ORGANIZATION_MODEL_SYSTEMS)
106
+ })
107
+
108
+ it('accepts valid responsible role, knowledge, and goal references', () => {
109
+ expect(() =>
110
+ resolveOrganizationModel({
111
+ roles: { roles: [{ id: 'role.sales-ops', title: 'Sales Ops' }] },
112
+ goals: {
113
+ objectives: [
114
+ {
115
+ id: 'goal.pipeline-coverage',
116
+ description: 'Improve sales pipeline coverage',
117
+ periodStart: '2026-01-01',
118
+ periodEnd: '2026-12-31'
119
+ }
120
+ ]
121
+ },
122
+ knowledge: {
123
+ nodes: [
124
+ {
125
+ id: 'knowledge.lead-gen-playbook',
126
+ kind: 'playbook',
127
+ title: 'Lead Gen Playbook',
128
+ summary: 'How lead generation is governed.',
129
+ body: '## Lead Gen',
130
+ updatedAt: '2026-05-08'
131
+ }
132
+ ]
133
+ },
134
+ systems: {
135
+ systems: [
136
+ {
137
+ ...VALID_SYSTEM,
138
+ responsibleRoleId: 'role.sales-ops',
139
+ governedByKnowledge: ['knowledge.lead-gen-playbook'],
140
+ drivesGoals: ['goal.pipeline-coverage']
141
+ }
142
+ ]
143
+ }
144
+ })
145
+ ).not.toThrow()
146
+ })
147
+
148
+ it('throws when system IDs are duplicated', () => {
149
+ expect(() =>
150
+ resolveOrganizationModel({
151
+ systems: {
152
+ systems: [
153
+ VALID_SYSTEM,
154
+ {
155
+ ...VALID_SYSTEM,
156
+ title: 'Duplicate System'
157
+ }
158
+ ]
159
+ }
160
+ })
161
+ ).toThrow(/System id \\"sys\.lead-gen\\" must be unique/)
162
+ })
163
+
164
+ it('throws when responsibleRoleId references an unknown role', () => {
165
+ expect(() =>
166
+ resolveOrganizationModel({
167
+ systems: {
168
+ systems: [{ ...VALID_SYSTEM, responsibleRoleId: 'role.missing' }]
169
+ }
170
+ })
171
+ ).toThrow(/unknown responsibleRoleId \\"role\.missing\\"/)
172
+ })
173
+
174
+ it('throws when governedByKnowledge references an unknown knowledge node', () => {
175
+ expect(() =>
176
+ resolveOrganizationModel({
177
+ systems: {
178
+ systems: [{ ...VALID_SYSTEM, governedByKnowledge: ['knowledge.missing'] }]
179
+ }
180
+ })
181
+ ).toThrow(/unknown knowledge node \\"knowledge\.missing\\"/)
182
+ })
183
+
184
+ it('throws when drivesGoals references an unknown goal', () => {
185
+ expect(() =>
186
+ resolveOrganizationModel({
187
+ systems: {
188
+ systems: [{ ...VALID_SYSTEM, drivesGoals: ['goal.missing'] }]
189
+ }
190
+ })
191
+ ).toThrow(/unknown goal \\"goal\.missing\\"/)
192
+ })
193
+ })
@@ -8,6 +8,7 @@ import {
8
8
  import { OrganizationGraphEdgeKindSchema } from '../graph/schema'
9
9
  import { buildOrganizationGraph } from '../graph/build'
10
10
  import { DEFAULT_ORGANIZATION_MODEL, DEFAULT_ORGANIZATION_MODEL_KNOWLEDGE } from '../defaults'
11
+ import { resolveOrganizationModel } from '../resolve'
11
12
  import type { OrganizationModel } from '../types'
12
13
 
13
14
  // ---------------------------------------------------------------------------
@@ -74,6 +75,15 @@ describe('OrgKnowledgeNodeSchema', () => {
74
75
  expect(node.ownerIds).toEqual([])
75
76
  })
76
77
 
78
+ it('accepts role ids as knowledge owners', () => {
79
+ const node = OrgKnowledgeNodeSchema.parse({
80
+ ...makeNode('strategy'),
81
+ ownerIds: ['role.ops-lead', 'role.ceo']
82
+ })
83
+
84
+ expect(node.ownerIds).toEqual(['role.ops-lead', 'role.ceo'])
85
+ })
86
+
77
87
  it('accepts an optional semantic icon token', () => {
78
88
  const node = OrgKnowledgeNodeSchema.parse({
79
89
  ...makeNode('playbook'),
@@ -93,6 +103,35 @@ describe('OrgKnowledgeNodeSchema', () => {
93
103
  })
94
104
  })
95
105
 
106
+ describe('resolveOrganizationModel with knowledge owner roles', () => {
107
+ const node = {
108
+ id: 'knowledge.test-owner',
109
+ kind: 'playbook' as const,
110
+ title: 'Owned Knowledge',
111
+ summary: 'A knowledge node with role ownership.',
112
+ body: '## Body\n\nContent.',
113
+ ownerIds: ['role.ops-lead'],
114
+ updatedAt: '2026-05-08'
115
+ }
116
+
117
+ it('passes when ownerIds reference declared role ids', () => {
118
+ expect(() =>
119
+ resolveOrganizationModel({
120
+ roles: { roles: [{ id: 'role.ops-lead', title: 'Ops Lead' }] },
121
+ knowledge: { nodes: [node] }
122
+ })
123
+ ).not.toThrow()
124
+ })
125
+
126
+ it('throws when ownerIds reference unknown role ids', () => {
127
+ expect(() =>
128
+ resolveOrganizationModel({
129
+ knowledge: { nodes: [node] }
130
+ })
131
+ ).toThrow(/unknown owner role/)
132
+ })
133
+ })
134
+
96
135
  // ---------------------------------------------------------------------------
97
136
  // OrgKnowledgeKindSchema — enum shape
98
137
  // ---------------------------------------------------------------------------
@@ -7,7 +7,7 @@ describe('organization-model resolve', () => {
7
7
 
8
8
  expect(model.version).toBe(1)
9
9
  expect(model.features.find((feature) => feature.id === 'dashboard')?.path).toBe('/')
10
- expect(model.features.find((feature) => feature.id === 'sales')?.path).toBeUndefined()
10
+ expect(model.features.find((feature) => feature.id === 'sales')?.path).toBe('/sales')
11
11
  expect(model.features.find((feature) => feature.id === 'sales.crm')?.path).toBe('/crm')
12
12
  expect(model.features.find((feature) => feature.id === 'admin')?.requiresAdmin).toBe(true)
13
13
  expect(model.features.find((feature) => feature.id === 'archive')?.devOnly).toBe(true)
@@ -20,6 +20,8 @@ import { DEFAULT_ORGANIZATION_MODEL_PROSPECTING } from './domains/prospecting'
20
20
  import { DEFAULT_ORGANIZATION_MODEL_OPERATIONS } from './domains/operations'
21
21
  import { DEFAULT_ORGANIZATION_MODEL_ROLES } from './domains/roles'
22
22
  import { DEFAULT_ORGANIZATION_MODEL_GOALS } from './domains/goals'
23
+ import { DEFAULT_ORGANIZATION_MODEL_SYSTEMS } from './domains/systems'
24
+ import { DEFAULT_ORGANIZATION_MODEL_RESOURCES } from './domains/resources'
23
25
  import { DEFAULT_ORGANIZATION_MODEL_STATUSES } from './domains/statuses'
24
26
  import type { KnowledgeDomain } from './domains/knowledge'
25
27
 
@@ -61,6 +63,15 @@ export const DEFAULT_ORGANIZATION_MODEL: OrganizationModel = {
61
63
  color: 'green',
62
64
  icon: 'feature.finance'
63
65
  },
66
+ {
67
+ id: 'business',
68
+ label: 'Business',
69
+ description: 'Revenue, client relationships, and project delivery',
70
+ enabled: true,
71
+ color: 'blue',
72
+ icon: 'feature.business',
73
+ uiPosition: 'sidebar-primary'
74
+ },
64
75
  {
65
76
  id: 'sales',
66
77
  label: 'Sales',
@@ -68,7 +79,7 @@ export const DEFAULT_ORGANIZATION_MODEL: OrganizationModel = {
68
79
  enabled: true,
69
80
  color: 'blue',
70
81
  icon: 'feature.sales',
71
- uiPosition: 'sidebar-primary'
82
+ path: '/sales'
72
83
  },
73
84
  {
74
85
  id: 'sales.crm',
@@ -95,8 +106,16 @@ export const DEFAULT_ORGANIZATION_MODEL: OrganizationModel = {
95
106
  enabled: true,
96
107
  color: 'orange',
97
108
  icon: 'feature.projects',
98
- path: '/projects',
99
- uiPosition: 'sidebar-primary'
109
+ path: '/projects'
110
+ },
111
+ {
112
+ id: 'clients',
113
+ label: 'Clients',
114
+ description: 'Client relationships, accounts, and business context',
115
+ enabled: true,
116
+ color: 'orange',
117
+ icon: 'feature.projects',
118
+ path: '/business/clients'
100
119
  },
101
120
  {
102
121
  id: 'operations',
@@ -341,6 +360,8 @@ export const DEFAULT_ORGANIZATION_MODEL: OrganizationModel = {
341
360
  offerings: DEFAULT_ORGANIZATION_MODEL_OFFERINGS,
342
361
  roles: DEFAULT_ORGANIZATION_MODEL_ROLES,
343
362
  goals: DEFAULT_ORGANIZATION_MODEL_GOALS,
363
+ systems: DEFAULT_ORGANIZATION_MODEL_SYSTEMS,
364
+ resources: DEFAULT_ORGANIZATION_MODEL_RESOURCES,
344
365
  statuses: DEFAULT_ORGANIZATION_MODEL_STATUSES,
345
366
  operations: DEFAULT_ORGANIZATION_MODEL_OPERATIONS,
346
367
  knowledge: DEFAULT_ORGANIZATION_MODEL_KNOWLEDGE
@@ -1,6 +1,7 @@
1
1
  import { z } from 'zod'
2
2
  import { IconNameSchema, ModelIdSchema } from './shared'
3
3
  import { NodeIdStringSchema } from './features'
4
+ import { RoleIdSchema } from './roles'
4
5
 
5
6
  // ---------------------------------------------------------------------------
6
7
  // KnowledgeLink — a typed graph link pointing to another OM node.
@@ -40,8 +41,8 @@ export const OrgKnowledgeNodeSchema = z.object({
40
41
  skills: z.array(KnowledgeSkillBindingSchema).optional(),
41
42
  /** Domain key used to derive fast graph->skill registries. */
42
43
  domain: KnowledgeDomainBindingSchema.optional(),
43
- /** Identifiers of the roles or members who own this knowledge node. */
44
- ownerIds: z.array(ModelIdSchema).default([]),
44
+ /** Role identifiers that own this knowledge node. */
45
+ ownerIds: z.array(RoleIdSchema).default([]),
45
46
  /** ISO date string (YYYY-MM-DD or full ISO 8601) of last meaningful update. */
46
47
  updatedAt: z.string().trim().min(1).max(50)
47
48
  })
@@ -0,0 +1,88 @@
1
+ import { z } from 'zod'
2
+ import { ModelIdSchema } from './shared'
3
+ import { SystemIdSchema } from './systems'
4
+
5
+ // ---------------------------------------------------------------------------
6
+ // Resources domain
7
+ // ---------------------------------------------------------------------------
8
+ //
9
+ // Resources are governance-only OM descriptors. Runtime code imports these
10
+ // descriptors and attaches executable behavior; it does not re-author identity.
11
+
12
+ export const ResourceKindSchema = z.enum(['workflow', 'agent', 'integration'])
13
+ export const ResourceGovernanceStatusSchema = z.enum(['active', 'deprecated', 'archived'])
14
+ export const AgentKindSchema = z.enum(['orchestrator', 'specialist', 'utility', 'system'])
15
+
16
+ export const ResourceIdSchema = z
17
+ .string()
18
+ .trim()
19
+ .min(1)
20
+ .max(255)
21
+ .regex(/^[A-Za-z0-9]+(?:[-._][A-Za-z0-9]+)*$/, 'Resource IDs must use letters, numbers, -, _, or . separators')
22
+
23
+ const ResourceEntryBaseSchema = z.object({
24
+ /** Canonical resource id; runtime resourceId derives from this value. */
25
+ id: ResourceIdSchema,
26
+ /** Required single System membership. */
27
+ systemId: SystemIdSchema,
28
+ /** Optional role responsible for maintaining this resource. */
29
+ ownerRoleId: ModelIdSchema.optional(),
30
+ status: ResourceGovernanceStatusSchema
31
+ })
32
+
33
+ export const WorkflowResourceEntrySchema = ResourceEntryBaseSchema.extend({
34
+ kind: z.literal('workflow'),
35
+ /** Mirrors WorkflowConfig.capabilityKey when the runtime workflow has one. */
36
+ capabilityKey: z.string().trim().min(1).max(255).optional()
37
+ })
38
+
39
+ export const AgentResourceEntrySchema = ResourceEntryBaseSchema.extend({
40
+ kind: z.literal('agent'),
41
+ /** Mirrors code-side AgentConfig.kind. */
42
+ agentKind: AgentKindSchema,
43
+ /** Role this agent embodies, if any. */
44
+ actsAsRoleId: ModelIdSchema.optional(),
45
+ /** Mirrors AgentConfig.sessionCapable. */
46
+ sessionCapable: z.boolean()
47
+ })
48
+
49
+ export const IntegrationResourceEntrySchema = ResourceEntryBaseSchema.extend({
50
+ kind: z.literal('integration'),
51
+ provider: z.string().trim().min(1).max(100)
52
+ })
53
+
54
+ export const ResourceEntrySchema = z.discriminatedUnion('kind', [
55
+ WorkflowResourceEntrySchema,
56
+ AgentResourceEntrySchema,
57
+ IntegrationResourceEntrySchema
58
+ ])
59
+
60
+ export const ResourcesDomainSchema = z.object({
61
+ entries: z.array(ResourceEntrySchema).default([])
62
+ })
63
+
64
+ export const DEFAULT_ORGANIZATION_MODEL_RESOURCES: z.infer<typeof ResourcesDomainSchema> = {
65
+ entries: []
66
+ }
67
+
68
+ export function defineResource<const TResource extends ResourceEntry>(resource: TResource): TResource {
69
+ return ResourceEntrySchema.parse(resource) as TResource
70
+ }
71
+
72
+ export function defineResources<const TResources extends Record<string, ResourceEntry>>(resources: TResources): TResources {
73
+ for (const resource of Object.values(resources)) {
74
+ ResourceEntrySchema.parse(resource)
75
+ }
76
+
77
+ return resources
78
+ }
79
+
80
+ export type ResourceId = z.infer<typeof ResourceIdSchema>
81
+ export type ResourceKind = z.infer<typeof ResourceKindSchema>
82
+ export type ResourceGovernanceStatus = z.infer<typeof ResourceGovernanceStatusSchema>
83
+ export type ResourceAgentKind = z.infer<typeof AgentKindSchema>
84
+ export type WorkflowResourceEntry = z.infer<typeof WorkflowResourceEntrySchema>
85
+ export type AgentResourceEntry = z.infer<typeof AgentResourceEntrySchema>
86
+ export type IntegrationResourceEntry = z.infer<typeof IntegrationResourceEntrySchema>
87
+ export type ResourceEntry = z.infer<typeof ResourceEntrySchema>
88
+ export type ResourcesDomain = z.infer<typeof ResourcesDomainSchema>
@@ -1,55 +1,93 @@
1
- import { z } from 'zod'
2
-
3
- // ---------------------------------------------------------------------------
4
- // Role schema one entry per distinct role in the organization's chart.
5
- // Inspired by the EOS Accountability Chart but uses plain-language field names
6
- // throughout. No EOS jargon: "title" (not seatTitle), "responsibilities"
7
- // (not accountabilities), "reportsToId", "heldBy".
8
- //
9
- // Cross-reference: `reportsToId` (when present) must resolve to another
10
- // `roles[].id` in the same collection. Enforcement is via
11
- // `OrganizationModelSchema.superRefine()` — not at the individual schema level.
12
- // Cycle detection is NOT enforced (known limitation; document if needed).
13
- // ---------------------------------------------------------------------------
14
-
15
- export const RoleSchema = z.object({
16
- /** Stable unique identifier for the role (e.g. "role-ceo", "role-head-of-sales"). */
17
- id: z.string().trim().min(1).max(100),
18
- /** Human-readable title shown to agents and in UI (e.g. "CEO", "Head of Sales"). */
19
- title: z.string().trim().min(1).max(200),
20
- /**
21
- * List of responsibilities this role owns — plain-language descriptions of
22
- * what the person in this role is accountable for delivering.
23
- * Defaults to empty array so minimal role definitions stay concise.
24
- */
25
- responsibilities: z.array(z.string().trim().max(500)).default([]),
26
- /**
27
- * Optional: ID of another role this role reports to.
28
- * When present, must reference another `roles[].id` in the same organization.
29
- * Cross-reference enforced in `OrganizationModelSchema.superRefine()`.
30
- * Absence indicates a top-level role (no reporting line).
31
- */
32
- reportsToId: z.string().trim().min(1).max(100).optional(),
33
- /**
34
- * Optional: name or email of the person currently holding this role.
35
- * Free-form string supports "Alice Johnson", "alice@example.com", or
36
- * any human-readable identifier. Not validated against any user registry.
37
- */
38
- heldBy: z.string().trim().max(200).optional()
39
- })
40
-
41
- // ---------------------------------------------------------------------------
42
- // Roles domain schema — a collection of roles.
43
- // ---------------------------------------------------------------------------
44
-
45
- export const RolesDomainSchema = z.object({
46
- roles: z.array(RoleSchema).default([])
47
- })
48
-
49
- // ---------------------------------------------------------------------------
50
- // Seed empty by default; adapters populate with real role definitions.
51
- // ---------------------------------------------------------------------------
52
-
53
- export const DEFAULT_ORGANIZATION_MODEL_ROLES: z.infer<typeof RolesDomainSchema> = {
54
- roles: []
55
- }
1
+ import { z } from 'zod'
2
+ import { ResourceIdSchema } from './resources'
3
+ import { ModelIdSchema } from './shared'
4
+ import { SystemIdSchema } from './systems'
5
+
6
+ // ---------------------------------------------------------------------------
7
+ // Role schema - one entry per distinct role in the organization's chart.
8
+ // Inspired by the EOS Accountability Chart but uses plain-language field names
9
+ // throughout. No EOS jargon: "title" (not seatTitle), "responsibilities"
10
+ // (not accountabilities), "reportsToId", "heldBy".
11
+ //
12
+ // Cross-reference enforcement lives in `OrganizationModelSchema.superRefine()`:
13
+ // `reportsToId` must resolve to another Role, `responsibleFor` entries must
14
+ // resolve to Systems, and agent holders must resolve to Agent Resources.
15
+ // Cycle detection is NOT enforced (known limitation; document if needed).
16
+ // ---------------------------------------------------------------------------
17
+
18
+ export const RoleIdSchema = ModelIdSchema
19
+
20
+ export const HumanRoleHolderSchema = z.object({
21
+ kind: z.literal('human'),
22
+ userId: z.string().trim().min(1).max(200)
23
+ })
24
+
25
+ export const AgentRoleHolderSchema = z.object({
26
+ kind: z.literal('agent'),
27
+ agentId: ResourceIdSchema
28
+ })
29
+
30
+ export const TeamRoleHolderSchema = z.object({
31
+ kind: z.literal('team'),
32
+ memberIds: z.array(z.string().trim().min(1).max(200)).min(1)
33
+ })
34
+
35
+ export const RoleHolderSchema = z.discriminatedUnion('kind', [
36
+ HumanRoleHolderSchema,
37
+ AgentRoleHolderSchema,
38
+ TeamRoleHolderSchema
39
+ ])
40
+
41
+ export const RoleHoldersSchema = z.union([RoleHolderSchema, z.array(RoleHolderSchema).min(1)])
42
+
43
+ export const RoleSchema = z.object({
44
+ /** Stable unique identifier for the role (e.g. "role-ceo", "role-head-of-sales"). */
45
+ id: RoleIdSchema,
46
+ /** Human-readable title shown to agents and in UI (e.g. "CEO", "Head of Sales"). */
47
+ title: z.string().trim().min(1).max(200),
48
+ /**
49
+ * List of responsibilities this role owns - plain-language descriptions of
50
+ * what the person in this role is accountable for delivering.
51
+ * Defaults to empty array so minimal role definitions stay concise.
52
+ */
53
+ responsibilities: z.array(z.string().trim().max(500)).default([]),
54
+ /**
55
+ * Optional: ID of another role this role reports to.
56
+ * When present, must reference another `roles[].id` in the same organization.
57
+ */
58
+ reportsToId: RoleIdSchema.optional(),
59
+ /**
60
+ * Optional: human, agent, or team holder currently filling this role.
61
+ * Agent holders reference OM Resource IDs and are validated at the model level.
62
+ */
63
+ heldBy: RoleHoldersSchema.optional(),
64
+ /**
65
+ * Optional Systems this role is accountable for.
66
+ * Cross-reference enforced in `OrganizationModelSchema.superRefine()`.
67
+ */
68
+ responsibleFor: z.array(SystemIdSchema).optional()
69
+ })
70
+
71
+ // ---------------------------------------------------------------------------
72
+ // Roles domain schema - a collection of roles.
73
+ // ---------------------------------------------------------------------------
74
+
75
+ export const RolesDomainSchema = z.object({
76
+ roles: z.array(RoleSchema).default([])
77
+ })
78
+
79
+ // ---------------------------------------------------------------------------
80
+ // Seed - empty by default; adapters populate with real role definitions.
81
+ // ---------------------------------------------------------------------------
82
+
83
+ export const DEFAULT_ORGANIZATION_MODEL_ROLES: z.infer<typeof RolesDomainSchema> = {
84
+ roles: []
85
+ }
86
+
87
+ export type RoleId = z.infer<typeof RoleIdSchema>
88
+ export type HumanRoleHolder = z.infer<typeof HumanRoleHolderSchema>
89
+ export type AgentRoleHolder = z.infer<typeof AgentRoleHolderSchema>
90
+ export type TeamRoleHolder = z.infer<typeof TeamRoleHolderSchema>
91
+ export type RoleHolder = z.infer<typeof RoleHolderSchema>
92
+ export type Role = z.infer<typeof RoleSchema>
93
+ export type RolesDomain = z.infer<typeof RolesDomainSchema>
@@ -0,0 +1,46 @@
1
+ import { z } from 'zod'
2
+ import { DescriptionSchema, LabelSchema, ModelIdSchema, ReferenceIdsSchema } from './shared'
3
+
4
+ // ---------------------------------------------------------------------------
5
+ // Systems domain
6
+ // ---------------------------------------------------------------------------
7
+ //
8
+ // A System is a tenant-defined bounded context that groups operational
9
+ // resources and carries governance metadata. The shared schema validates the
10
+ // shape and references; each tenant supplies its own catalog.
11
+
12
+ export const SystemKindSchema = z.enum(['product', 'operational', 'platform', 'diagnostic'])
13
+ export const SystemStatusSchema = z.enum(['active', 'deprecated', 'archived'])
14
+ export const SystemIdSchema = ModelIdSchema
15
+
16
+ export const SystemEntrySchema = z.object({
17
+ /** Stable tenant-defined system id (e.g. "sys.lead-gen"). */
18
+ id: SystemIdSchema,
19
+ /** Human-readable system title shown in governance and operations UI. */
20
+ title: LabelSchema,
21
+ /** One-paragraph purpose statement for the bounded context. */
22
+ description: DescriptionSchema,
23
+ /** Closed system shape enum; catalog values remain tenant-defined. */
24
+ kind: SystemKindSchema,
25
+ /** Optional role responsible for this system. */
26
+ responsibleRoleId: ModelIdSchema.optional(),
27
+ /** Optional knowledge nodes that govern this system. */
28
+ governedByKnowledge: ReferenceIdsSchema,
29
+ /** Optional goals this system contributes to. */
30
+ drivesGoals: ReferenceIdsSchema,
31
+ status: SystemStatusSchema
32
+ })
33
+
34
+ export const SystemsDomainSchema = z.object({
35
+ systems: z.array(SystemEntrySchema).default([])
36
+ })
37
+
38
+ export const DEFAULT_ORGANIZATION_MODEL_SYSTEMS: z.infer<typeof SystemsDomainSchema> = {
39
+ systems: []
40
+ }
41
+
42
+ export type SystemId = z.infer<typeof SystemIdSchema>
43
+ export type SystemKind = z.infer<typeof SystemKindSchema>
44
+ export type SystemStatus = z.infer<typeof SystemStatusSchema>
45
+ export type SystemEntry = z.infer<typeof SystemEntrySchema>
46
+ export type SystemsDomain = z.infer<typeof SystemsDomainSchema>
@@ -18,6 +18,7 @@ export const ORGANIZATION_MODEL_ICON_TOKENS = [
18
18
  'knowledge.reference',
19
19
  'feature.dashboard',
20
20
  'feature.calendar',
21
+ 'feature.business',
21
22
  'feature.sales',
22
23
  'feature.crm',
23
24
  'feature.finance',
@@ -16,3 +16,5 @@ export * from './domains/sales'
16
16
  export * from './domains/projects'
17
17
  export * from './domains/features'
18
18
  export * from './domains/prospecting'
19
+ export * from './domains/systems'
20
+ export * from './domains/resources'