@elevasis/core 0.18.0 → 0.20.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 (54) hide show
  1. package/dist/index.d.ts +82 -1
  2. package/dist/index.js +353 -171
  3. package/dist/knowledge/index.d.ts +44 -1
  4. package/dist/organization-model/index.d.ts +82 -1
  5. package/dist/organization-model/index.js +353 -171
  6. package/dist/test-utils/index.d.ts +41 -12
  7. package/dist/test-utils/index.js +352 -171
  8. package/package.json +4 -3
  9. package/src/_gen/__tests__/__snapshots__/contracts.md.snap +89 -69
  10. package/src/auth/multi-tenancy/organizations/__tests__/api-schemas.test.ts +194 -0
  11. package/src/auth/multi-tenancy/organizations/api-schemas.ts +136 -128
  12. package/src/business/acquisition/api-schemas.test.ts +199 -15
  13. package/src/business/acquisition/api-schemas.ts +116 -51
  14. package/src/business/acquisition/build-templates.test.ts +212 -0
  15. package/src/business/acquisition/derive-actions.test.ts +1 -1
  16. package/src/business/acquisition/types.ts +21 -38
  17. package/src/business/deals/api-schemas.ts +2 -2
  18. package/src/execution/engine/index.ts +436 -434
  19. package/src/execution/engine/tools/integration/server/adapters/google-calendar/google-calendar-adapter.ts +428 -0
  20. package/src/execution/engine/tools/integration/server/adapters/google-calendar/index.ts +2 -0
  21. package/src/execution/engine/tools/lead-service-types.ts +51 -9
  22. package/src/execution/engine/tools/platform/acquisition/company-tools.ts +7 -6
  23. package/src/execution/engine/tools/platform/acquisition/contact-tools.ts +6 -5
  24. package/src/execution/engine/tools/platform/acquisition/types.ts +20 -9
  25. package/src/execution/engine/tools/registry.ts +700 -698
  26. package/src/execution/engine/tools/tool-maps.ts +10 -0
  27. package/src/execution/external/__tests__/api-schemas.test.ts +127 -0
  28. package/src/integrations/oauth/__tests__/provider-registry.test.ts +7 -6
  29. package/src/integrations/oauth/provider-registry.ts +74 -61
  30. package/src/integrations/oauth/server/credentials.ts +43 -39
  31. package/src/knowledge/__tests__/queries.test.ts +89 -0
  32. package/src/organization-model/__tests__/graph.test.ts +108 -2
  33. package/src/organization-model/__tests__/icons.test.ts +61 -0
  34. package/src/organization-model/__tests__/knowledge.test.ts +118 -1
  35. package/src/organization-model/__tests__/prospecting-ssot.test.ts +91 -0
  36. package/src/organization-model/__tests__/schema.test.ts +122 -0
  37. package/src/organization-model/__tests__/surface-projection.test.ts +174 -0
  38. package/src/organization-model/defaults.ts +8 -0
  39. package/src/organization-model/domains/knowledge.ts +9 -0
  40. package/src/organization-model/domains/prospecting.ts +347 -226
  41. package/src/organization-model/domains/sales.ts +40 -30
  42. package/src/organization-model/graph/build.ts +74 -0
  43. package/src/organization-model/graph/schema.ts +1 -0
  44. package/src/organization-model/graph/types.ts +1 -0
  45. package/src/organization-model/icons.ts +3 -0
  46. package/src/organization-model/schema.ts +63 -0
  47. package/src/organization-model/surface-projection.ts +218 -0
  48. package/src/organization-model/types.ts +9 -1
  49. package/src/platform/constants/versions.ts +1 -1
  50. package/src/platform/utils/__tests__/validation.test.ts +1084 -1083
  51. package/src/platform/utils/validation.ts +425 -425
  52. package/src/reference/_generated/contracts.md +89 -69
  53. package/src/server.ts +6 -0
  54. package/src/supabase/database.types.ts +6 -12
@@ -195,6 +195,18 @@ export type OrgKnowledgeNode = z.infer<typeof OrgKnowledgeNodeSchema>
195
195
  export type OrgKnowledgeKind = z.infer<typeof OrgKnowledgeKindSchema>
196
196
  ```
197
197
 
198
+ ### `KnowledgeSkillBinding`
199
+
200
+ ```typescript
201
+ export type KnowledgeSkillBinding = z.infer<typeof KnowledgeSkillBindingSchema>
202
+ ```
203
+
204
+ ### `KnowledgeDomainBinding`
205
+
206
+ ```typescript
207
+ export type KnowledgeDomainBinding = z.infer<typeof KnowledgeDomainBindingSchema>
208
+ ```
209
+
198
210
  ### `OrganizationModelIconToken`
199
211
 
200
212
  ```typescript
@@ -866,7 +878,9 @@ export interface AcqCompany {
866
878
  category: string | null
867
879
  categoryPain: string | null
868
880
  segment: string | null
869
- pipelineStatus: CompanyPipelineStatus | null
881
+ processingState: CompanyProcessingState | null
882
+ /** @deprecated Use `processingState`. This legacy DB column has been removed; responses return null. */
883
+ pipelineStatus?: LegacyPipelineStatus | null
870
884
  enrichmentData: CompanyEnrichmentData | null
871
885
  source: string | null
872
886
  batchId: string | null
@@ -906,7 +920,9 @@ export interface AcqContact {
906
920
  openingLine: string | null
907
921
  source: string | null
908
922
  sourceId: string | null
909
- pipelineStatus: ContactPipelineStatus | null
923
+ processingState: ContactProcessingState | null
924
+ /** @deprecated Use `processingState`. This legacy DB column has been removed; responses return null. */
925
+ pipelineStatus?: LegacyPipelineStatus | null
910
926
  enrichmentData: ContactEnrichmentData | null
911
927
  /** Attio Person record ID - set when contact responds and is added to CRM */
912
928
  attioPersonId: string | null
@@ -969,7 +985,7 @@ export interface DealContact {
969
985
  title: string | null
970
986
  headline: string | null
971
987
  linkedin_url: string | null
972
- pipeline_status: Record<string, unknown> | null
988
+ processing_state: Record<string, unknown> | null
973
989
  enrichment_data: Record<string, unknown> | null
974
990
  company: {
975
991
  id: string
@@ -1107,7 +1123,7 @@ export const DEFAULT_CRM_PRIORITY_RULE_CONFIG: CrmPriorityRuleConfig = {
1107
1123
  ### `DealStageSchema`
1108
1124
 
1109
1125
  ```typescript
1110
- export const DealStageSchema = z.enum(['interested', 'proposal', 'closing', 'closed_won', 'closed_lost', 'nurturing'])
1126
+ export const DealStageSchema = CrmStageKeySchema
1111
1127
  ```
1112
1128
 
1113
1129
  ### `AcqDealTaskKindSchema`
@@ -1199,7 +1215,7 @@ export const TransitionItemRequestSchema = z
1199
1215
  .object({
1200
1216
  pipelineKey: z.string().min(1),
1201
1217
  stageKey: z.string().min(1),
1202
- stateKey: z.string().nullable().optional(),
1218
+ stateKey: z.string().min(1).nullable().optional(),
1203
1219
  reason: z.string().optional(),
1204
1220
  expectedUpdatedAt: z.string().datetime().optional()
1205
1221
  })
@@ -1242,7 +1258,7 @@ export const DealContactSummarySchema = z.object({
1242
1258
  title: z.string().nullable(),
1243
1259
  headline: z.string().nullable(),
1244
1260
  linkedin_url: z.string().nullable(),
1245
- pipeline_status: z.record(z.string(), z.unknown()).nullable(),
1261
+ processing_state: ProcessingStateSchema.nullable(),
1246
1262
  enrichment_data: z.record(z.string(), z.unknown()).nullable(),
1247
1263
  company: z
1248
1264
  .object({
@@ -1393,6 +1409,11 @@ export const DealTaskListResponseSchema = z.array(DealTaskResponseSchema)
1393
1409
 
1394
1410
  ```typescript
1395
1411
  export const DealSchemas = {
1412
+ // Primitives
1413
+ CrmStageKey: CrmStageKeySchema,
1414
+ CrmStateKey: CrmStateKeySchema,
1415
+ DealStage: DealStageSchema,
1416
+
1396
1417
  // Params
1397
1418
  DealIdParams: DealIdParamsSchema,
1398
1419
  DealTaskIdParams: DealTaskIdParamsSchema,
@@ -1405,7 +1426,7 @@ export const DealSchemas = {
1405
1426
  // Request bodies
1406
1427
  CreateDealNoteRequest: CreateDealNoteRequestSchema,
1407
1428
  CreateDealTaskRequest: CreateDealTaskRequestSchema,
1408
- TransitionItemRequest: TransitionItemRequestSchema,
1429
+ TransitionItemRequest: CrmTransitionItemRequestSchema,
1409
1430
  TransitionDealStateRequest: TransitionDealStateRequestSchema,
1410
1431
  ExecuteActionParams: ExecuteActionParamsSchema,
1411
1432
  ExecuteActionRequest: ExecuteActionRequestSchema,
@@ -1570,51 +1591,6 @@ export interface WebPost {
1570
1591
  }
1571
1592
  ```
1572
1593
 
1573
- ### `CompanyPipelineStatus`
1574
-
1575
- ```typescript
1576
- /**
1577
- * Tracks pipeline status for a company across all processing stages.
1578
- */
1579
- export interface CompanyPipelineStatus {
1580
- acquired: boolean
1581
- enrichment: {
1582
- [source: string]: {
1583
- status: 'pending' | 'complete' | 'failed' | 'skipped'
1584
- completedAt?: string
1585
- error?: string
1586
- }
1587
- }
1588
- }
1589
- ```
1590
-
1591
- ### `ContactPipelineStatus`
1592
-
1593
- ```typescript
1594
- /**
1595
- * Tracks pipeline status for a contact across all processing stages.
1596
- */
1597
- export interface ContactPipelineStatus {
1598
- enrichment: {
1599
- [source: string]: {
1600
- status: 'pending' | 'complete' | 'failed' | 'skipped'
1601
- completedAt?: string
1602
- error?: string
1603
- }
1604
- }
1605
- personalization: {
1606
- status: 'pending' | 'complete' | 'failed' | 'skipped'
1607
- completedAt?: string
1608
- }
1609
- outreach: {
1610
- status: 'pending' | 'sent' | 'replied' | 'bounced' | 'opted-out'
1611
- sentAt?: string
1612
- channel?: string
1613
- campaignId?: string
1614
- }
1615
- }
1616
- ```
1617
-
1618
1594
  ### `CompanyEnrichmentData`
1619
1595
 
1620
1596
  ```typescript
@@ -1730,7 +1706,9 @@ export interface AcqCompany {
1730
1706
  category: string | null
1731
1707
  categoryPain: string | null
1732
1708
  segment: string | null
1733
- pipelineStatus: CompanyPipelineStatus | null
1709
+ processingState: CompanyProcessingState | null
1710
+ /** @deprecated Use `processingState`. This legacy DB column has been removed; responses return null. */
1711
+ pipelineStatus?: LegacyPipelineStatus | null
1734
1712
  enrichmentData: CompanyEnrichmentData | null
1735
1713
  source: string | null
1736
1714
  batchId: string | null
@@ -1770,7 +1748,9 @@ export interface AcqContact {
1770
1748
  openingLine: string | null
1771
1749
  source: string | null
1772
1750
  sourceId: string | null
1773
- pipelineStatus: ContactPipelineStatus | null
1751
+ processingState: ContactProcessingState | null
1752
+ /** @deprecated Use `processingState`. This legacy DB column has been removed; responses return null. */
1753
+ pipelineStatus?: LegacyPipelineStatus | null
1774
1754
  enrichmentData: ContactEnrichmentData | null
1775
1755
  /** Attio Person record ID - set when contact responds and is added to CRM */
1776
1756
  attioPersonId: string | null
@@ -2081,6 +2061,7 @@ export const ListCompaniesQuerySchema = z
2081
2061
  website: z.string().trim().min(1).max(2048).optional(),
2082
2062
  segment: z.string().trim().min(1).max(255).optional(),
2083
2063
  category: z.string().trim().min(1).max(255).optional(),
2064
+ pipelineStatus: z.unknown().optional(),
2084
2065
  batchId: z.string().trim().min(1).max(255).optional(),
2085
2066
  status: AcqCompanyStatusSchema.optional(),
2086
2067
  includeAll: QueryBooleanSchema.optional(),
@@ -2122,6 +2103,7 @@ export const CreateCompanyRequestSchema = z
2122
2103
  category: z.string().trim().min(1).max(255).optional(),
2123
2104
  source: z.string().trim().min(1).max(255).optional(),
2124
2105
  batchId: z.string().trim().min(1).max(255).optional(),
2106
+ pipelineStatus: z.unknown().optional(),
2125
2107
  verticalResearch: z.string().trim().min(1).max(5000).optional()
2126
2108
  })
2127
2109
  .strict()
@@ -2142,7 +2124,8 @@ export const UpdateCompanyRequestSchema = z
2142
2124
  locationState: z.string().trim().min(1).max(255).optional(),
2143
2125
  category: z.string().trim().min(1).max(255).optional(),
2144
2126
  segment: z.string().trim().min(1).max(255).optional(),
2145
- pipelineStatus: z.record(z.string(), z.unknown()).optional(),
2127
+ processingState: CompanyProcessingStateSchema.optional(),
2128
+ pipelineStatus: z.unknown().optional(),
2146
2129
  enrichmentData: z.record(z.string(), z.unknown()).optional(),
2147
2130
  source: z.string().trim().min(1).max(255).optional(),
2148
2131
  batchId: z.string().trim().min(1).max(255).optional(),
@@ -2162,6 +2145,7 @@ export const UpdateCompanyRequestSchema = z
2162
2145
  data.locationState !== undefined ||
2163
2146
  data.category !== undefined ||
2164
2147
  data.segment !== undefined ||
2148
+ data.processingState !== undefined ||
2165
2149
  data.pipelineStatus !== undefined ||
2166
2150
  data.enrichmentData !== undefined ||
2167
2151
  data.source !== undefined ||
@@ -2187,7 +2171,8 @@ export const CreateContactRequestSchema = z
2187
2171
  title: z.string().trim().min(1).max(255).optional(),
2188
2172
  source: z.string().trim().min(1).max(255).optional(),
2189
2173
  sourceId: z.string().trim().min(1).max(255).optional(),
2190
- batchId: z.string().trim().min(1).max(255).optional()
2174
+ batchId: z.string().trim().min(1).max(255).optional(),
2175
+ pipelineStatus: z.unknown().optional()
2191
2176
  })
2192
2177
  .strict()
2193
2178
  ```
@@ -2206,7 +2191,8 @@ export const UpdateContactRequestSchema = z
2206
2191
  headline: z.string().trim().min(1).max(5000).optional(),
2207
2192
  filterReason: z.string().trim().min(1).max(5000).optional(),
2208
2193
  openingLine: z.string().trim().min(1).max(5000).optional(),
2209
- pipelineStatus: z.record(z.string(), z.unknown()).optional(),
2194
+ processingState: ContactProcessingStateSchema.optional(),
2195
+ pipelineStatus: z.unknown().optional(),
2210
2196
  enrichmentData: z.record(z.string(), z.unknown()).optional(),
2211
2197
  status: AcqContactStatusSchema.optional()
2212
2198
  })
@@ -2222,6 +2208,7 @@ export const UpdateContactRequestSchema = z
2222
2208
  data.headline !== undefined ||
2223
2209
  data.filterReason !== undefined ||
2224
2210
  data.openingLine !== undefined ||
2211
+ data.processingState !== undefined ||
2225
2212
  data.pipelineStatus !== undefined ||
2226
2213
  data.enrichmentData !== undefined ||
2227
2214
  data.status !== undefined,
@@ -2248,7 +2235,8 @@ export const AcqCompanyResponseSchema = z.object({
2248
2235
  category: z.string().nullable(),
2249
2236
  categoryPain: z.string().nullable(),
2250
2237
  segment: z.string().nullable(),
2251
- pipelineStatus: z.record(z.string(), z.unknown()).nullable(),
2238
+ processingState: CompanyProcessingStateSchema.nullable(),
2239
+ pipelineStatus: z.unknown().nullable().optional(),
2252
2240
  enrichmentData: z.record(z.string(), z.unknown()).nullable(),
2253
2241
  source: z.string().nullable(),
2254
2242
  batchId: z.string().nullable(),
@@ -2314,7 +2302,8 @@ export const AcqContactResponseSchema = z.object({
2314
2302
  openingLine: z.string().nullable(),
2315
2303
  source: z.string().nullable(),
2316
2304
  sourceId: z.string().nullable(),
2317
- pipelineStatus: z.record(z.string(), z.unknown()).nullable(),
2305
+ processingState: ContactProcessingStateSchema.nullable(),
2306
+ pipelineStatus: z.unknown().nullable().optional(),
2318
2307
  enrichmentData: z.record(z.string(), z.unknown()).nullable(),
2319
2308
  attioPersonId: z.string().nullable(),
2320
2309
  batchId: z.string().nullable(),
@@ -2480,6 +2469,7 @@ export const AcqListCompanyResponseSchema = z.object({
2480
2469
 
2481
2470
  ```typescript
2482
2471
  export const AcqCompanySchemas = {
2472
+ CompanyProcessingState: CompanyProcessingStateSchema,
2483
2473
  CompanyIdParams: CompanyIdParamsSchema,
2484
2474
  ListCompaniesQuery: ListCompaniesQuerySchema,
2485
2475
  CreateCompanyRequest: CreateCompanyRequestSchema,
@@ -2494,6 +2484,7 @@ export const AcqCompanySchemas = {
2494
2484
 
2495
2485
  ```typescript
2496
2486
  export const AcqContactSchemas = {
2487
+ ContactProcessingState: ContactProcessingStateSchema,
2497
2488
  ContactIdParams: ContactIdParamsSchema,
2498
2489
  ListContactsQuery: ListContactsQuerySchema,
2499
2490
  CreateContactRequest: CreateContactRequestSchema,
@@ -2519,7 +2510,10 @@ export const AcqListSchemas = {
2519
2510
  BuildPlanSnapshot: BuildPlanSnapshotSchema,
2520
2511
  BuildPlanSnapshotStep: BuildPlanSnapshotStepSchema,
2521
2512
  AcqListMetadata: AcqListMetadataSchema,
2513
+ LeadGenStageKey: LeadGenStageKeySchema,
2514
+ LeadGenCapabilityKey: LeadGenCapabilityKeySchema,
2522
2515
  ProcessingStageStatus: ProcessingStageStatusSchema,
2516
+ ProcessingState: ProcessingStateSchema,
2523
2517
  ListStageCounts: ListStageCountsSchema,
2524
2518
  ListTelemetry: ListTelemetrySchema,
2525
2519
 
@@ -2563,7 +2557,7 @@ export const AcqSubstrateSchemas = {
2563
2557
  ListCompanyIdParams: ListCompanyIdParamsSchema,
2564
2558
  AcqListCompanyResponse: AcqListCompanyResponseSchema,
2565
2559
 
2566
- // Transition (shared with deals — TransitionItemRequestSchema)
2560
+ // Transition (generic stateful substrate)
2567
2561
  TransitionItemRequest: TransitionItemRequestSchema
2568
2562
  }
2569
2563
  ```
@@ -2631,6 +2625,8 @@ export interface StatefulStageDefinition {
2631
2625
  /** Matches stage_key values written by workflow steps. */
2632
2626
  stageKey: string
2633
2627
  label: string
2628
+ /** UI color token. Consumers may map this to their design system. */
2629
+ color?: string
2634
2630
  states: StatefulStateDefinition[]
2635
2631
  }
2636
2632
  ```
@@ -2812,6 +2808,8 @@ export interface CreateCompanyParams {
2812
2808
  source?: string
2813
2809
  batchId?: string
2814
2810
  verticalResearch?: string
2811
+ /** @deprecated Use processingState. Accepted as a no-op compatibility bridge for external tenants. */
2812
+ pipelineStatus?: unknown
2815
2813
  }
2816
2814
  ```
2817
2815
 
@@ -2829,7 +2827,9 @@ export interface UpdateCompanyParams {
2829
2827
  locationState?: string
2830
2828
  category?: string
2831
2829
  segment?: string
2832
- pipelineStatus?: Record<string, unknown>
2830
+ processingState?: ProcessingState
2831
+ /** @deprecated Use processingState. Accepted as a no-op compatibility bridge for external tenants. */
2832
+ pipelineStatus?: unknown
2833
2833
  enrichmentData?: Record<string, unknown>
2834
2834
  source?: string
2835
2835
  batchId?: string
@@ -2837,7 +2837,7 @@ export interface UpdateCompanyParams {
2837
2837
  verticalResearch?: string | null
2838
2838
  /** Track A: flat qualification score column (null until a scoring rubric is defined) */
2839
2839
  qualificationScore?: number | null
2840
- /** Track A: flat qualification signals jsonb — mirrors the former pipeline_status.qualification shape */
2840
+ /** Track A: flat qualification signals jsonb */
2841
2841
  qualificationSignals?: Record<string, unknown> | null
2842
2842
  /** Track A: key identifying the rubric used for qualification */
2843
2843
  qualificationRubricKey?: string | null
@@ -2860,13 +2860,15 @@ export interface CompanyFilters {
2860
2860
  website?: string
2861
2861
  segment?: string
2862
2862
  category?: string
2863
- pipelineStatus?: Record<string, unknown>
2864
- /** Exclude companies whose pipeline_status contains this value (PostgREST NOT contains) */
2865
- pipelineStatusNot?: Record<string, unknown>
2863
+ processingState?: ProcessingState
2864
+ /** @deprecated Use processingState. Accepted as a no-op compatibility bridge for external tenants. */
2865
+ pipelineStatus?: unknown
2866
+ /** Exclude companies whose processing state contains this value (PostgREST NOT contains) */
2867
+ processingStateNot?: ProcessingState
2866
2868
  batchId?: string
2867
2869
  status?: 'active' | 'invalid'
2868
2870
  includeAll?: boolean
2869
- excludeColumns?: Array<'enrichmentData' | 'pipelineStatus'>
2871
+ excludeColumns?: Array<'enrichmentData' | 'processingState'>
2870
2872
  limit?: number
2871
2873
  }
2872
2874
  ```
@@ -2885,6 +2887,8 @@ export interface CreateContactParams {
2885
2887
  source?: string
2886
2888
  sourceId?: string
2887
2889
  batchId?: string
2890
+ /** @deprecated Use processingState. Accepted as a no-op compatibility bridge for external tenants. */
2891
+ pipelineStatus?: unknown
2888
2892
  }
2889
2893
  ```
2890
2894
 
@@ -2901,7 +2905,9 @@ export interface UpdateContactParams {
2901
2905
  headline?: string
2902
2906
  filterReason?: string
2903
2907
  openingLine?: string
2904
- pipelineStatus?: Record<string, unknown>
2908
+ processingState?: ProcessingState
2909
+ /** @deprecated Use processingState. Accepted as a no-op compatibility bridge for external tenants. */
2910
+ pipelineStatus?: unknown
2905
2911
  enrichmentData?: Record<string, unknown>
2906
2912
  status?: 'active' | 'invalid'
2907
2913
  }
@@ -2920,7 +2926,9 @@ export interface ContactFilters {
2920
2926
  listId?: string // Filter to contacts in a specific list (via acq_list_members)
2921
2927
  search?: string
2922
2928
  openingLineIsNull?: boolean // Filter to contacts without personalization
2923
- pipelineStatus?: Record<string, unknown>
2929
+ processingState?: ProcessingState
2930
+ /** @deprecated Use processingState. Accepted as a no-op compatibility bridge for external tenants. */
2931
+ pipelineStatus?: unknown
2924
2932
  batchId?: string
2925
2933
  contactStatus?: 'active' | 'invalid' // Filter by contact status (soft-delete flag)
2926
2934
  }
@@ -3119,7 +3127,7 @@ export interface BulkImportCompanyEntry {
3119
3127
  category?: string
3120
3128
  source?: string
3121
3129
  enrichmentData?: Record<string, unknown>
3122
- pipelineStatus?: Record<string, unknown>
3130
+ processingState?: ProcessingState
3123
3131
  }
3124
3132
  ```
3125
3133
 
@@ -3300,6 +3308,14 @@ export type ListToolMap = {
3300
3308
  params: Omit<UpdateContactStageParams, 'organizationId'>
3301
3309
  result: void
3302
3310
  }
3311
+ listPendingCompanyIds: {
3312
+ params: Omit<ListPendingCompanyIdsParams, 'organizationId'>
3313
+ result: string[]
3314
+ }
3315
+ listPendingContactIds: {
3316
+ params: Omit<ListPendingContactIdsParams, 'organizationId'>
3317
+ result: string[]
3318
+ }
3303
3319
  }
3304
3320
  ```
3305
3321
 
@@ -3335,6 +3351,10 @@ export const OrgKnowledgeNodeSchema = z.object({
3335
3351
  * Each link emits a `governs` edge: knowledge-node -> target node.
3336
3352
  */
3337
3353
  links: z.array(KnowledgeLinkSchema).default([]),
3354
+ /** Operator skill or command bindings relevant to this node. */
3355
+ skills: z.array(KnowledgeSkillBindingSchema).optional(),
3356
+ /** Domain key used to derive fast graph->skill registries. */
3357
+ domain: KnowledgeDomainBindingSchema.optional(),
3338
3358
  /** Identifiers of the roles or members who own this knowledge node. */
3339
3359
  ownerIds: z.array(ModelIdSchema).default([]),
3340
3360
  /** ISO date string (YYYY-MM-DD or full ISO 8601) of last meaningful update. */
@@ -0,0 +1,194 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import { CreateOrganizationSchema, ListOrganizationsQuerySchema, UpdateOrganizationSchema } from '../api-schemas'
3
+
4
+ // ---------------------------------------------------------------------------
5
+ // CreateOrganizationSchema
6
+ // ---------------------------------------------------------------------------
7
+
8
+ describe('CreateOrganizationSchema', () => {
9
+ it('accepts a minimal valid payload (name only)', () => {
10
+ expect(CreateOrganizationSchema.safeParse({ name: 'Acme Corp' }).success).toBe(true)
11
+ })
12
+
13
+ it('accepts name at minimum length boundary (2 chars)', () => {
14
+ expect(CreateOrganizationSchema.safeParse({ name: 'AB' }).success).toBe(true)
15
+ })
16
+
17
+ it('accepts name at maximum length boundary (100 chars)', () => {
18
+ expect(CreateOrganizationSchema.safeParse({ name: 'A'.repeat(100) }).success).toBe(true)
19
+ })
20
+
21
+ it('rejects name shorter than 2 characters', () => {
22
+ expect(CreateOrganizationSchema.safeParse({ name: 'A' }).success).toBe(false)
23
+ expect(CreateOrganizationSchema.safeParse({ name: '' }).success).toBe(false)
24
+ })
25
+
26
+ it('rejects name longer than 100 characters', () => {
27
+ expect(CreateOrganizationSchema.safeParse({ name: 'A'.repeat(101) }).success).toBe(false)
28
+ })
29
+
30
+ it('accepts names with letters, numbers, spaces, hyphens, and underscores', () => {
31
+ expect(CreateOrganizationSchema.safeParse({ name: 'My Org-123_Test' }).success).toBe(true)
32
+ })
33
+
34
+ it('rejects names with disallowed characters (special symbols)', () => {
35
+ expect(CreateOrganizationSchema.safeParse({ name: 'Acme@Corp' }).success).toBe(false)
36
+ expect(CreateOrganizationSchema.safeParse({ name: 'Acme.Corp' }).success).toBe(false)
37
+ expect(CreateOrganizationSchema.safeParse({ name: 'Acme/Corp' }).success).toBe(false)
38
+ expect(CreateOrganizationSchema.safeParse({ name: 'Acme+Corp' }).success).toBe(false)
39
+ })
40
+
41
+ it('accepts optional domainData array with valid entries', () => {
42
+ const result = CreateOrganizationSchema.safeParse({
43
+ name: 'Acme Corp',
44
+ domainData: [{ domain: 'acme.com', state: 'verified' }]
45
+ })
46
+ expect(result.success).toBe(true)
47
+ })
48
+
49
+ it('accepts domainData up to 10 entries', () => {
50
+ const domains = Array.from({ length: 10 }, (_, i) => ({ domain: `domain${i}.com` }))
51
+ expect(CreateOrganizationSchema.safeParse({ name: 'Acme Corp', domainData: domains }).success).toBe(true)
52
+ })
53
+
54
+ it('rejects domainData with more than 10 entries', () => {
55
+ const domains = Array.from({ length: 11 }, (_, i) => ({ domain: `domain${i}.com` }))
56
+ expect(CreateOrganizationSchema.safeParse({ name: 'Acme Corp', domainData: domains }).success).toBe(false)
57
+ })
58
+
59
+ it('accepts optional metadata as a record under 10KB', () => {
60
+ const result = CreateOrganizationSchema.safeParse({
61
+ name: 'Acme Corp',
62
+ metadata: { plan: 'pro', region: 'us-west' }
63
+ })
64
+ expect(result.success).toBe(true)
65
+ })
66
+
67
+ it('rejects metadata exceeding 10KB (10240 bytes)', () => {
68
+ // Build a metadata object whose JSON representation exceeds 10240 bytes
69
+ const largeValue = 'x'.repeat(10241)
70
+ const result = CreateOrganizationSchema.safeParse({
71
+ name: 'Acme Corp',
72
+ metadata: { big: largeValue }
73
+ })
74
+ expect(result.success).toBe(false)
75
+ })
76
+
77
+ it('rejects unknown top-level fields (.strict() mode)', () => {
78
+ expect(CreateOrganizationSchema.safeParse({ name: 'Acme Corp', unknownField: 'value' }).success).toBe(false)
79
+ })
80
+
81
+ it('rejects missing name field', () => {
82
+ expect(CreateOrganizationSchema.safeParse({}).success).toBe(false)
83
+ })
84
+ })
85
+
86
+ // ---------------------------------------------------------------------------
87
+ // UpdateOrganizationSchema
88
+ // ---------------------------------------------------------------------------
89
+
90
+ describe('UpdateOrganizationSchema', () => {
91
+ it('rejects an empty object (at least one field required)', () => {
92
+ // UpdateOrganizationSchema is CreateOrganizationSchema.partial().strict().refine(...)
93
+ // The .refine() enforces the documented "at least one field" convention
94
+ // (see .claude/rules/core-package.md → "api-schemas.ts Pattern")
95
+ const result = UpdateOrganizationSchema.safeParse({})
96
+ expect(result.success).toBe(false)
97
+ if (!result.success) {
98
+ expect(result.error.issues[0].message).toMatch(/at least one field/i)
99
+ }
100
+ })
101
+
102
+ it('accepts updating only name', () => {
103
+ expect(UpdateOrganizationSchema.safeParse({ name: 'New Name' }).success).toBe(true)
104
+ })
105
+
106
+ it('accepts updating only domainData', () => {
107
+ expect(UpdateOrganizationSchema.safeParse({ domainData: [{ domain: 'newdomain.com' }] }).success).toBe(true)
108
+ })
109
+
110
+ it('accepts updating only metadata', () => {
111
+ expect(UpdateOrganizationSchema.safeParse({ metadata: { key: 'value' } }).success).toBe(true)
112
+ })
113
+
114
+ it('applies same name validation as CreateOrganizationSchema when name is provided', () => {
115
+ // Too short
116
+ expect(UpdateOrganizationSchema.safeParse({ name: 'A' }).success).toBe(false)
117
+ // Too long
118
+ expect(UpdateOrganizationSchema.safeParse({ name: 'A'.repeat(101) }).success).toBe(false)
119
+ // Invalid charset
120
+ expect(UpdateOrganizationSchema.safeParse({ name: 'Bad@Name' }).success).toBe(false)
121
+ // Valid
122
+ expect(UpdateOrganizationSchema.safeParse({ name: 'Good Name-123' }).success).toBe(true)
123
+ })
124
+
125
+ it('applies same domainData max-10 validation when provided', () => {
126
+ const tooMany = Array.from({ length: 11 }, (_, i) => ({ domain: `d${i}.com` }))
127
+ expect(UpdateOrganizationSchema.safeParse({ domainData: tooMany }).success).toBe(false)
128
+ })
129
+
130
+ it('applies same metadata 10KB cap validation when provided', () => {
131
+ const largeValue = 'x'.repeat(10241)
132
+ expect(UpdateOrganizationSchema.safeParse({ metadata: { big: largeValue } }).success).toBe(false)
133
+ })
134
+
135
+ it('rejects unknown top-level fields (.strict() mode)', () => {
136
+ expect(UpdateOrganizationSchema.safeParse({ name: 'Valid Name', unknownField: 'bad' }).success).toBe(false)
137
+ })
138
+ })
139
+
140
+ // ---------------------------------------------------------------------------
141
+ // ListOrganizationsQuerySchema
142
+ // ---------------------------------------------------------------------------
143
+
144
+ describe('ListOrganizationsQuerySchema', () => {
145
+ it('accepts an empty query and applies default limit of 20', () => {
146
+ const result = ListOrganizationsQuerySchema.safeParse({})
147
+ expect(result.success).toBe(true)
148
+ if (result.success) {
149
+ expect(result.data.limit).toBe(20)
150
+ }
151
+ })
152
+
153
+ it('coerces limit from string "50" to number 50', () => {
154
+ const result = ListOrganizationsQuerySchema.safeParse({ limit: '50' })
155
+ expect(result.success).toBe(true)
156
+ if (result.success) expect(result.data.limit).toBe(50)
157
+ })
158
+
159
+ it('accepts limit at upper boundary (100)', () => {
160
+ const result = ListOrganizationsQuerySchema.safeParse({ limit: '100' })
161
+ expect(result.success).toBe(true)
162
+ if (result.success) expect(result.data.limit).toBe(100)
163
+ })
164
+
165
+ it('accepts limit at lower boundary (1)', () => {
166
+ const result = ListOrganizationsQuerySchema.safeParse({ limit: '1' })
167
+ expect(result.success).toBe(true)
168
+ if (result.success) expect(result.data.limit).toBe(1)
169
+ })
170
+
171
+ it('rejects limit of 101 (above max 100)', () => {
172
+ expect(ListOrganizationsQuerySchema.safeParse({ limit: '101' }).success).toBe(false)
173
+ })
174
+
175
+ it('rejects limit of 0 (below min 1)', () => {
176
+ expect(ListOrganizationsQuerySchema.safeParse({ limit: '0' }).success).toBe(false)
177
+ })
178
+
179
+ it('accepts optional before cursor string', () => {
180
+ const result = ListOrganizationsQuerySchema.safeParse({ before: 'cursor_abc123' })
181
+ expect(result.success).toBe(true)
182
+ if (result.success) expect(result.data.before).toBe('cursor_abc123')
183
+ })
184
+
185
+ it('accepts optional after cursor string', () => {
186
+ const result = ListOrganizationsQuerySchema.safeParse({ after: 'cursor_xyz789' })
187
+ expect(result.success).toBe(true)
188
+ if (result.success) expect(result.data.after).toBe('cursor_xyz789')
189
+ })
190
+
191
+ it('accepts both before and after cursors together', () => {
192
+ expect(ListOrganizationsQuerySchema.safeParse({ before: 'a', after: 'b' }).success).toBe(true)
193
+ })
194
+ })