@elevasis/sdk 1.16.0 → 1.18.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.
@@ -198,6 +198,18 @@ export type OrgKnowledgeNode = z.infer<typeof OrgKnowledgeNodeSchema>
198
198
  export type OrgKnowledgeKind = z.infer<typeof OrgKnowledgeKindSchema>
199
199
  ```
200
200
 
201
+ ### `KnowledgeSkillBinding`
202
+
203
+ ```typescript
204
+ export type KnowledgeSkillBinding = z.infer<typeof KnowledgeSkillBindingSchema>
205
+ ```
206
+
207
+ ### `KnowledgeDomainBinding`
208
+
209
+ ```typescript
210
+ export type KnowledgeDomainBinding = z.infer<typeof KnowledgeDomainBindingSchema>
211
+ ```
212
+
201
213
  ### `OrganizationModelIconToken`
202
214
 
203
215
  ```typescript
@@ -869,7 +881,9 @@ export interface AcqCompany {
869
881
  category: string | null
870
882
  categoryPain: string | null
871
883
  segment: string | null
872
- pipelineStatus: CompanyPipelineStatus | null
884
+ processingState: CompanyProcessingState | null
885
+ /** @deprecated Use `processingState`. This legacy DB column has been removed; responses return null. */
886
+ pipelineStatus?: LegacyPipelineStatus | null
873
887
  enrichmentData: CompanyEnrichmentData | null
874
888
  source: string | null
875
889
  batchId: string | null
@@ -909,7 +923,9 @@ export interface AcqContact {
909
923
  openingLine: string | null
910
924
  source: string | null
911
925
  sourceId: string | null
912
- pipelineStatus: ContactPipelineStatus | null
926
+ processingState: ContactProcessingState | null
927
+ /** @deprecated Use `processingState`. This legacy DB column has been removed; responses return null. */
928
+ pipelineStatus?: LegacyPipelineStatus | null
913
929
  enrichmentData: ContactEnrichmentData | null
914
930
  /** Attio Person record ID - set when contact responds and is added to CRM */
915
931
  attioPersonId: string | null
@@ -972,7 +988,7 @@ export interface DealContact {
972
988
  title: string | null
973
989
  headline: string | null
974
990
  linkedin_url: string | null
975
- pipeline_status: Record<string, unknown> | null
991
+ processing_state: Record<string, unknown> | null
976
992
  enrichment_data: Record<string, unknown> | null
977
993
  company: {
978
994
  id: string
@@ -1245,7 +1261,7 @@ export const DealContactSummarySchema = z.object({
1245
1261
  title: z.string().nullable(),
1246
1262
  headline: z.string().nullable(),
1247
1263
  linkedin_url: z.string().nullable(),
1248
- pipeline_status: z.record(z.string(), z.unknown()).nullable(),
1264
+ processing_state: ProcessingStateSchema.nullable(),
1249
1265
  enrichment_data: z.record(z.string(), z.unknown()).nullable(),
1250
1266
  company: z
1251
1267
  .object({
@@ -1573,51 +1589,6 @@ export interface WebPost {
1573
1589
  }
1574
1590
  ```
1575
1591
 
1576
- ### `CompanyPipelineStatus`
1577
-
1578
- ```typescript
1579
- /**
1580
- * Tracks pipeline status for a company across all processing stages.
1581
- */
1582
- export interface CompanyPipelineStatus {
1583
- acquired: boolean
1584
- enrichment: {
1585
- [source: string]: {
1586
- status: 'pending' | 'complete' | 'failed' | 'skipped'
1587
- completedAt?: string
1588
- error?: string
1589
- }
1590
- }
1591
- }
1592
- ```
1593
-
1594
- ### `ContactPipelineStatus`
1595
-
1596
- ```typescript
1597
- /**
1598
- * Tracks pipeline status for a contact across all processing stages.
1599
- */
1600
- export interface ContactPipelineStatus {
1601
- enrichment: {
1602
- [source: string]: {
1603
- status: 'pending' | 'complete' | 'failed' | 'skipped'
1604
- completedAt?: string
1605
- error?: string
1606
- }
1607
- }
1608
- personalization: {
1609
- status: 'pending' | 'complete' | 'failed' | 'skipped'
1610
- completedAt?: string
1611
- }
1612
- outreach: {
1613
- status: 'pending' | 'sent' | 'replied' | 'bounced' | 'opted-out'
1614
- sentAt?: string
1615
- channel?: string
1616
- campaignId?: string
1617
- }
1618
- }
1619
- ```
1620
-
1621
1592
  ### `CompanyEnrichmentData`
1622
1593
 
1623
1594
  ```typescript
@@ -1733,7 +1704,9 @@ export interface AcqCompany {
1733
1704
  category: string | null
1734
1705
  categoryPain: string | null
1735
1706
  segment: string | null
1736
- pipelineStatus: CompanyPipelineStatus | null
1707
+ processingState: CompanyProcessingState | null
1708
+ /** @deprecated Use `processingState`. This legacy DB column has been removed; responses return null. */
1709
+ pipelineStatus?: LegacyPipelineStatus | null
1737
1710
  enrichmentData: CompanyEnrichmentData | null
1738
1711
  source: string | null
1739
1712
  batchId: string | null
@@ -1773,7 +1746,9 @@ export interface AcqContact {
1773
1746
  openingLine: string | null
1774
1747
  source: string | null
1775
1748
  sourceId: string | null
1776
- pipelineStatus: ContactPipelineStatus | null
1749
+ processingState: ContactProcessingState | null
1750
+ /** @deprecated Use `processingState`. This legacy DB column has been removed; responses return null. */
1751
+ pipelineStatus?: LegacyPipelineStatus | null
1777
1752
  enrichmentData: ContactEnrichmentData | null
1778
1753
  /** Attio Person record ID - set when contact responds and is added to CRM */
1779
1754
  attioPersonId: string | null
@@ -2084,6 +2059,7 @@ export const ListCompaniesQuerySchema = z
2084
2059
  website: z.string().trim().min(1).max(2048).optional(),
2085
2060
  segment: z.string().trim().min(1).max(255).optional(),
2086
2061
  category: z.string().trim().min(1).max(255).optional(),
2062
+ pipelineStatus: z.unknown().optional(),
2087
2063
  batchId: z.string().trim().min(1).max(255).optional(),
2088
2064
  status: AcqCompanyStatusSchema.optional(),
2089
2065
  includeAll: QueryBooleanSchema.optional(),
@@ -2125,6 +2101,7 @@ export const CreateCompanyRequestSchema = z
2125
2101
  category: z.string().trim().min(1).max(255).optional(),
2126
2102
  source: z.string().trim().min(1).max(255).optional(),
2127
2103
  batchId: z.string().trim().min(1).max(255).optional(),
2104
+ pipelineStatus: z.unknown().optional(),
2128
2105
  verticalResearch: z.string().trim().min(1).max(5000).optional()
2129
2106
  })
2130
2107
  .strict()
@@ -2145,7 +2122,8 @@ export const UpdateCompanyRequestSchema = z
2145
2122
  locationState: z.string().trim().min(1).max(255).optional(),
2146
2123
  category: z.string().trim().min(1).max(255).optional(),
2147
2124
  segment: z.string().trim().min(1).max(255).optional(),
2148
- pipelineStatus: z.record(z.string(), z.unknown()).optional(),
2125
+ processingState: CompanyProcessingStateSchema.optional(),
2126
+ pipelineStatus: z.unknown().optional(),
2149
2127
  enrichmentData: z.record(z.string(), z.unknown()).optional(),
2150
2128
  source: z.string().trim().min(1).max(255).optional(),
2151
2129
  batchId: z.string().trim().min(1).max(255).optional(),
@@ -2165,6 +2143,7 @@ export const UpdateCompanyRequestSchema = z
2165
2143
  data.locationState !== undefined ||
2166
2144
  data.category !== undefined ||
2167
2145
  data.segment !== undefined ||
2146
+ data.processingState !== undefined ||
2168
2147
  data.pipelineStatus !== undefined ||
2169
2148
  data.enrichmentData !== undefined ||
2170
2149
  data.source !== undefined ||
@@ -2190,7 +2169,8 @@ export const CreateContactRequestSchema = z
2190
2169
  title: z.string().trim().min(1).max(255).optional(),
2191
2170
  source: z.string().trim().min(1).max(255).optional(),
2192
2171
  sourceId: z.string().trim().min(1).max(255).optional(),
2193
- batchId: z.string().trim().min(1).max(255).optional()
2172
+ batchId: z.string().trim().min(1).max(255).optional(),
2173
+ pipelineStatus: z.unknown().optional()
2194
2174
  })
2195
2175
  .strict()
2196
2176
  ```
@@ -2209,7 +2189,8 @@ export const UpdateContactRequestSchema = z
2209
2189
  headline: z.string().trim().min(1).max(5000).optional(),
2210
2190
  filterReason: z.string().trim().min(1).max(5000).optional(),
2211
2191
  openingLine: z.string().trim().min(1).max(5000).optional(),
2212
- pipelineStatus: z.record(z.string(), z.unknown()).optional(),
2192
+ processingState: ContactProcessingStateSchema.optional(),
2193
+ pipelineStatus: z.unknown().optional(),
2213
2194
  enrichmentData: z.record(z.string(), z.unknown()).optional(),
2214
2195
  status: AcqContactStatusSchema.optional()
2215
2196
  })
@@ -2225,6 +2206,7 @@ export const UpdateContactRequestSchema = z
2225
2206
  data.headline !== undefined ||
2226
2207
  data.filterReason !== undefined ||
2227
2208
  data.openingLine !== undefined ||
2209
+ data.processingState !== undefined ||
2228
2210
  data.pipelineStatus !== undefined ||
2229
2211
  data.enrichmentData !== undefined ||
2230
2212
  data.status !== undefined,
@@ -2251,7 +2233,8 @@ export const AcqCompanyResponseSchema = z.object({
2251
2233
  category: z.string().nullable(),
2252
2234
  categoryPain: z.string().nullable(),
2253
2235
  segment: z.string().nullable(),
2254
- pipelineStatus: z.record(z.string(), z.unknown()).nullable(),
2236
+ processingState: CompanyProcessingStateSchema.nullable(),
2237
+ pipelineStatus: z.unknown().nullable().optional(),
2255
2238
  enrichmentData: z.record(z.string(), z.unknown()).nullable(),
2256
2239
  source: z.string().nullable(),
2257
2240
  batchId: z.string().nullable(),
@@ -2317,7 +2300,8 @@ export const AcqContactResponseSchema = z.object({
2317
2300
  openingLine: z.string().nullable(),
2318
2301
  source: z.string().nullable(),
2319
2302
  sourceId: z.string().nullable(),
2320
- pipelineStatus: z.record(z.string(), z.unknown()).nullable(),
2303
+ processingState: ContactProcessingStateSchema.nullable(),
2304
+ pipelineStatus: z.unknown().nullable().optional(),
2321
2305
  enrichmentData: z.record(z.string(), z.unknown()).nullable(),
2322
2306
  attioPersonId: z.string().nullable(),
2323
2307
  batchId: z.string().nullable(),
@@ -2483,6 +2467,7 @@ export const AcqListCompanyResponseSchema = z.object({
2483
2467
 
2484
2468
  ```typescript
2485
2469
  export const AcqCompanySchemas = {
2470
+ CompanyProcessingState: CompanyProcessingStateSchema,
2486
2471
  CompanyIdParams: CompanyIdParamsSchema,
2487
2472
  ListCompaniesQuery: ListCompaniesQuerySchema,
2488
2473
  CreateCompanyRequest: CreateCompanyRequestSchema,
@@ -2497,6 +2482,7 @@ export const AcqCompanySchemas = {
2497
2482
 
2498
2483
  ```typescript
2499
2484
  export const AcqContactSchemas = {
2485
+ ContactProcessingState: ContactProcessingStateSchema,
2500
2486
  ContactIdParams: ContactIdParamsSchema,
2501
2487
  ListContactsQuery: ListContactsQuerySchema,
2502
2488
  CreateContactRequest: CreateContactRequestSchema,
@@ -2522,7 +2508,10 @@ export const AcqListSchemas = {
2522
2508
  BuildPlanSnapshot: BuildPlanSnapshotSchema,
2523
2509
  BuildPlanSnapshotStep: BuildPlanSnapshotStepSchema,
2524
2510
  AcqListMetadata: AcqListMetadataSchema,
2511
+ LeadGenStageKey: LeadGenStageKeySchema,
2512
+ LeadGenCapabilityKey: LeadGenCapabilityKeySchema,
2525
2513
  ProcessingStageStatus: ProcessingStageStatusSchema,
2514
+ ProcessingState: ProcessingStateSchema,
2526
2515
  ListStageCounts: ListStageCountsSchema,
2527
2516
  ListTelemetry: ListTelemetrySchema,
2528
2517
 
@@ -2815,6 +2804,8 @@ export interface CreateCompanyParams {
2815
2804
  source?: string
2816
2805
  batchId?: string
2817
2806
  verticalResearch?: string
2807
+ /** @deprecated Use processingState. Accepted as a no-op compatibility bridge for external tenants. */
2808
+ pipelineStatus?: unknown
2818
2809
  }
2819
2810
  ```
2820
2811
 
@@ -2832,7 +2823,9 @@ export interface UpdateCompanyParams {
2832
2823
  locationState?: string
2833
2824
  category?: string
2834
2825
  segment?: string
2835
- pipelineStatus?: Record<string, unknown>
2826
+ processingState?: ProcessingState
2827
+ /** @deprecated Use processingState. Accepted as a no-op compatibility bridge for external tenants. */
2828
+ pipelineStatus?: unknown
2836
2829
  enrichmentData?: Record<string, unknown>
2837
2830
  source?: string
2838
2831
  batchId?: string
@@ -2840,7 +2833,7 @@ export interface UpdateCompanyParams {
2840
2833
  verticalResearch?: string | null
2841
2834
  /** Track A: flat qualification score column (null until a scoring rubric is defined) */
2842
2835
  qualificationScore?: number | null
2843
- /** Track A: flat qualification signals jsonb — mirrors the former pipeline_status.qualification shape */
2836
+ /** Track A: flat qualification signals jsonb */
2844
2837
  qualificationSignals?: Record<string, unknown> | null
2845
2838
  /** Track A: key identifying the rubric used for qualification */
2846
2839
  qualificationRubricKey?: string | null
@@ -2863,13 +2856,15 @@ export interface CompanyFilters {
2863
2856
  website?: string
2864
2857
  segment?: string
2865
2858
  category?: string
2866
- pipelineStatus?: Record<string, unknown>
2867
- /** Exclude companies whose pipeline_status contains this value (PostgREST NOT contains) */
2868
- pipelineStatusNot?: Record<string, unknown>
2859
+ processingState?: ProcessingState
2860
+ /** @deprecated Use processingState. Accepted as a no-op compatibility bridge for external tenants. */
2861
+ pipelineStatus?: unknown
2862
+ /** Exclude companies whose processing state contains this value (PostgREST NOT contains) */
2863
+ processingStateNot?: ProcessingState
2869
2864
  batchId?: string
2870
2865
  status?: 'active' | 'invalid'
2871
2866
  includeAll?: boolean
2872
- excludeColumns?: Array<'enrichmentData' | 'pipelineStatus'>
2867
+ excludeColumns?: Array<'enrichmentData' | 'processingState'>
2873
2868
  limit?: number
2874
2869
  }
2875
2870
  ```
@@ -2888,6 +2883,8 @@ export interface CreateContactParams {
2888
2883
  source?: string
2889
2884
  sourceId?: string
2890
2885
  batchId?: string
2886
+ /** @deprecated Use processingState. Accepted as a no-op compatibility bridge for external tenants. */
2887
+ pipelineStatus?: unknown
2891
2888
  }
2892
2889
  ```
2893
2890
 
@@ -2904,7 +2901,9 @@ export interface UpdateContactParams {
2904
2901
  headline?: string
2905
2902
  filterReason?: string
2906
2903
  openingLine?: string
2907
- pipelineStatus?: Record<string, unknown>
2904
+ processingState?: ProcessingState
2905
+ /** @deprecated Use processingState. Accepted as a no-op compatibility bridge for external tenants. */
2906
+ pipelineStatus?: unknown
2908
2907
  enrichmentData?: Record<string, unknown>
2909
2908
  status?: 'active' | 'invalid'
2910
2909
  }
@@ -2923,7 +2922,9 @@ export interface ContactFilters {
2923
2922
  listId?: string // Filter to contacts in a specific list (via acq_list_members)
2924
2923
  search?: string
2925
2924
  openingLineIsNull?: boolean // Filter to contacts without personalization
2926
- pipelineStatus?: Record<string, unknown>
2925
+ processingState?: ProcessingState
2926
+ /** @deprecated Use processingState. Accepted as a no-op compatibility bridge for external tenants. */
2927
+ pipelineStatus?: unknown
2927
2928
  batchId?: string
2928
2929
  contactStatus?: 'active' | 'invalid' // Filter by contact status (soft-delete flag)
2929
2930
  }
@@ -3122,7 +3123,7 @@ export interface BulkImportCompanyEntry {
3122
3123
  category?: string
3123
3124
  source?: string
3124
3125
  enrichmentData?: Record<string, unknown>
3125
- pipelineStatus?: Record<string, unknown>
3126
+ processingState?: ProcessingState
3126
3127
  }
3127
3128
  ```
3128
3129
 
@@ -3303,6 +3304,14 @@ export type ListToolMap = {
3303
3304
  params: Omit<UpdateContactStageParams, 'organizationId'>
3304
3305
  result: void
3305
3306
  }
3307
+ listPendingCompanyIds: {
3308
+ params: Omit<ListPendingCompanyIdsParams, 'organizationId'>
3309
+ result: string[]
3310
+ }
3311
+ listPendingContactIds: {
3312
+ params: Omit<ListPendingContactIdsParams, 'organizationId'>
3313
+ result: string[]
3314
+ }
3306
3315
  }
3307
3316
  ```
3308
3317
 
@@ -3338,6 +3347,10 @@ export const OrgKnowledgeNodeSchema = z.object({
3338
3347
  * Each link emits a `governs` edge: knowledge-node -> target node.
3339
3348
  */
3340
3349
  links: z.array(KnowledgeLinkSchema).default([]),
3350
+ /** Operator skill or command bindings relevant to this node. */
3351
+ skills: z.array(KnowledgeSkillBindingSchema).optional(),
3352
+ /** Domain key used to derive fast graph->skill registries. */
3353
+ domain: KnowledgeDomainBindingSchema.optional(),
3341
3354
  /** Identifiers of the roles or members who own this knowledge node. */
3342
3355
  ownerIds: z.array(ModelIdSchema).default([]),
3343
3356
  /** ISO date string (YYYY-MM-DD or full ISO 8601) of last meaningful update. */
@@ -31,7 +31,7 @@ Feature IDs defined in `DEFAULT_ORGANIZATION_MODEL.features` (`packages/core/src
31
31
 
32
32
  ```typescript
33
33
  // FeatureSchema: { id, label, enabled, color?, icon?, entityIds, surfaceIds, resourceIds, capabilityIds }
34
- type DefaultFeatureId = 'dashboard' | 'identity' | 'platform' | 'finance' | 'sales' | 'sales.crm' | 'sales.lead-gen' | 'projects' | 'operations' | 'knowledge.command-view' | 'operations.overview' | 'operations.resources' | 'operations.command-queue' | 'operations.sessions' | 'operations.task-scheduler' | 'monitoring' | 'monitoring.activity-log' | 'monitoring.execution-logs' | 'monitoring.execution-health' | 'monitoring.cost-analytics' | 'monitoring.notifications' | 'monitoring.submitted-requests' | 'settings' | 'settings.account' | 'settings.appearance' | 'settings.roles' | 'settings.organization' | 'settings.credentials' | 'settings.api-keys' | 'settings.webhooks' | 'settings.deployments' | 'admin' | 'admin.system-health' | 'admin.organizations' | 'admin.users' | 'admin.design-showcase' | 'admin.debug' | 'archive' | 'archive.agent-chat' | 'archive.execution-runner' | 'seo' | 'knowledge' | 'knowledge.base'
34
+ type DefaultFeatureId = 'dashboard' | 'identity' | 'platform' | 'finance' | 'sales' | 'sales.crm' | 'sales.lead-gen' | 'projects' | 'operations' | 'knowledge.command-view' | 'operations.overview' | 'operations.resources' | 'operations.command-queue' | 'operations.sessions' | 'operations.task-scheduler' | 'monitoring' | 'monitoring.calendar' | 'monitoring.activity-log' | 'monitoring.execution-logs' | 'monitoring.execution-health' | 'monitoring.cost-analytics' | 'monitoring.notifications' | 'monitoring.submitted-requests' | 'settings' | 'settings.account' | 'settings.appearance' | 'settings.roles' | 'settings.organization' | 'settings.credentials' | 'settings.api-keys' | 'settings.webhooks' | 'settings.deployments' | 'admin' | 'admin.system-health' | 'admin.organizations' | 'admin.users' | 'admin.design-showcase' | 'admin.debug' | 'archive' | 'archive.agent-chat' | 'archive.execution-runner' | 'seo' | 'knowledge' | 'knowledge.base'
35
35
  ```
36
36
 
37
37
  These are the built-in feature IDs. The `featureId` field on a `FeatureModule` manifest must match the `id` of a feature in the organization model to enable access-flag gating.
@@ -0,0 +1,99 @@
1
+ ---
2
+ title: Spine Primer
3
+ description: Tenant-facing primer for using an Organization Model catalog as the shared coordination spine between workflow producers, runtime state, API filters, and UI projections.
4
+ ---
5
+ <!-- @generated by packages/sdk/scripts/copy-reference-docs.mjs -- DO NOT EDIT -->
6
+ <!-- Regenerate: pnpm scaffold:sync -->
7
+
8
+
9
+ # Spine Primer
10
+
11
+ Use this primer when a tenant domain has a closed set of stages, statuses, or catalog keys that multiple surfaces must share. A spine is the tenant Organization Model vocabulary that coordinates workflows, runtime state, API reads, and UI rendering without forcing those surfaces to import from each other.
12
+
13
+ The lead-generation pattern is the reference shape: `organizationModel.prospecting.stages` defines the stage vocabulary, workflow templates reference those stages, workflow factories validate stage keys before running, entity state stores sparse per-stage progress, and UI views render progress from the same vocabulary.
14
+
15
+ ## What The Spine Owns
16
+
17
+ A spine owns the shared vocabulary for one domain. The vocabulary is not just a list of strings. Each key should carry enough metadata for consumers to derive behavior consistently:
18
+
19
+ - `key` identifies the stage or catalog member.
20
+ - `label` gives a human-readable name.
21
+ - `description` explains the business meaning.
22
+ - `order` gives stable display and workflow ordering.
23
+ - `entity` identifies which tenant entity the stage updates.
24
+
25
+ The same keys then appear in four places:
26
+
27
+ | Role | Tenant-facing shape |
28
+ | --- | --- |
29
+ | Vocabulary | `organizationModel.prospecting.stages` |
30
+ | Process | workflow templates and build steps that reference stage keys |
31
+ | Runtime state | entity state maps such as `{ [stageKey]: { status, updatedAt, data } }` |
32
+ | Consumer projection | API filters, progress summaries, and UI step rendering |
33
+
34
+ ## Layering
35
+
36
+ ```text
37
+ tenant Organization Model
38
+ - prospecting stages
39
+ - workflow templates
40
+ - capability bindings
41
+ |
42
+ +--> workflow factory
43
+ | - validates stage keys
44
+ | - runs producers
45
+ | - dispatches entity-tagged results
46
+ |
47
+ +--> API and query helpers
48
+ | - filter pending entities by stage key
49
+ | - aggregate progress from state maps
50
+ |
51
+ +--> UI projections
52
+ - render labels and order from the catalog
53
+ - distinguish known and unknown state keys
54
+
55
+ entity runtime state
56
+ { [stageKey]: { status, updatedAt, data } }
57
+ ```
58
+
59
+ The important boundary is that producers and consumers coordinate through the Organization Model vocabulary plus runtime state. They do not coordinate through direct imports, duplicated string constants, or workflow-specific side channels.
60
+
61
+ ## Invariants
62
+
63
+ 1. **The vocabulary is closed and validated.** Workflow factories should reject stage keys that are not present in the tenant Organization Model.
64
+
65
+ 2. **The vocabulary carries metadata.** Labels, descriptions, ordering, and entity ownership should come from the catalog rather than being copied into each consumer.
66
+
67
+ 3. **Runtime state is sparse and additive.** New stages appear in entity state when reached. Adding a catalog member should not require backfilling every entity.
68
+
69
+ 4. **Producers return entity-tagged results.** Workflow steps should return which entity changed, its identifier, status, and optional data. The factory owns writing those results to the correct state map.
70
+
71
+ 5. **Capability binding is separate from stage naming.** Workflow templates should point at capability keys, and the tenant environment can bind those capabilities to concrete workflow resources.
72
+
73
+ ## Applying The Pattern
74
+
75
+ Use this checklist when adding or changing a tenant domain spine:
76
+
77
+ 1. Define the domain catalog in the tenant Organization Model.
78
+ 2. Include labels, descriptions, ordering, and entity ownership in each catalog entry.
79
+ 3. Make workflow templates reference catalog keys instead of free-form strings.
80
+ 4. Validate template stage keys when constructing workflow factories.
81
+ 5. Store progress in sparse entity state maps keyed by catalog member.
82
+ 6. Return entity-tagged workflow results and let the factory dispatch state updates.
83
+ 7. Read progress through query helpers that use the same catalog keys.
84
+ 8. Render UI labels, order, and status from the catalog plus state map.
85
+
86
+ Promote a vocabulary to a spine when the same catalog coordinates all three surfaces: at least one process definition, at least one workflow producer, and at least one independent consumer such as an API query or UI view.
87
+
88
+ ## Counter-Patterns
89
+
90
+ Avoid these patterns in spine-governed domains:
91
+
92
+ - Free-form stage strings in workflow inputs.
93
+ - Duplicated UI-only labels or ordering.
94
+ - Per-workflow skip logic that bypasses the shared pending and progress contract.
95
+ - State writes from individual steps instead of the workflow factory.
96
+ - Hidden side channels for progress, such as ad hoc context keys.
97
+ - Direct coupling between producer code and consumer UI code.
98
+
99
+ When in doubt, update the Organization Model vocabulary first, then let workflow templates, factories, queries, and views project from it.