@elevasis/sdk 1.45.0 → 1.46.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.
- package/dist/cli.cjs +36046 -31587
- package/dist/index.d.ts +833 -391
- package/dist/index.js +59 -59
- package/dist/node/index.d.ts +0 -84
- package/dist/node/index.js +2 -2
- package/dist/test-utils/index.d.ts +859 -331
- package/dist/test-utils/index.js +166 -134
- package/dist/worker/index.d.ts +897 -324
- package/dist/worker/index.js +68 -35
- package/package.json +4 -4
- package/reference/_navigation.md +11 -2
- package/reference/_reference-manifest.json +42 -0
- package/reference/core/exports.mdx +2 -0
- package/reference/packages/core/src/business/README.md +4 -1
- package/reference/packages/core/src/content/README.md +19 -0
- package/reference/packages/core/src/organization-model/README.md +148 -149
- package/reference/packages/core/src/organization-model/readiness/README.md +42 -0
- package/reference/packages/ui/src/features/README.md +28 -28
- package/reference/rules/shared-types.md +21 -0
- package/reference/scaffold/core/organization-graph.mdx +2 -3
- package/reference/scaffold/core/organization-model.mdx +2 -6
- package/reference/scaffold/operations/propagation-pipeline.md +15 -16
- package/reference/scaffold/operations/scaffold-maintenance.md +3 -2
- package/reference/scaffold/operations/workflow-recipes.md +2 -2
- package/reference/scaffold/recipes/customize-crm-actions.md +5 -5
- package/reference/scaffold/recipes/extend-content.md +265 -0
- package/reference/scaffold/recipes/extend-lead-gen.md +14 -16
- package/reference/scaffold/recipes/index.md +4 -1
- package/reference/scaffold/reference/contracts.md +18 -55
- package/reference/scaffold/reference/feature-registry.md +3 -0
- package/reference/scaffold/reference/glossary.md +1 -1
- package/reference/scaffold/ui/customization.md +2 -2
- package/reference/scaffold/ui/feature-shell.mdx +1 -3
- package/reference/sdk/cli-management.mdx +90 -5
- package/reference/sdk/cli.mdx +90 -13
- package/reference/sdk/framework/agent.mdx +6 -0
- package/reference/sdk/platform-tools/adapters-platform.mdx +3 -1
- package/reference/ui/exports.mdx +1 -0
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Build and Extend Content
|
|
3
|
+
description: Map the content platform primitives available to SDK projects: pipeline and step catalogs, shared review/pipeline/distribution pages, data hooks, the content workflow adapter, artifacts as rules documents, and org-model extension boundaries.
|
|
4
|
+
---
|
|
5
|
+
<!-- @generated by packages/sdk/scripts/copy-reference-docs.mjs -- DO NOT EDIT -->
|
|
6
|
+
<!-- Regenerate: pnpm scaffold:sync -->
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
# Build and Extend Content
|
|
10
|
+
|
|
11
|
+
Use this recipe when a downstream project wants to build on the shared content system instead of forking it.
|
|
12
|
+
|
|
13
|
+
Good trigger phrases:
|
|
14
|
+
|
|
15
|
+
- "Add a content pipeline for our podcast."
|
|
16
|
+
- "Change the review screen for clip proposals."
|
|
17
|
+
- "Write a workflow that produces the next step of a content item."
|
|
18
|
+
- "Store our style guide so producers read the same rules."
|
|
19
|
+
- "Track where each piece got published."
|
|
20
|
+
|
|
21
|
+
Content is a layered platform surface, not one component:
|
|
22
|
+
|
|
23
|
+
- **Organization OS:** pipelines, steps, statuses, pillars, and platforms are **catalogs** in the content System's ontology, not hardcoded enums. `content.config.defaultPipelineId` names the pipeline the UI opens by default.
|
|
24
|
+
- **Shared UI:** overview, items list, item review, pipelines, pipeline workspace, distributions, and rules-and-artifacts pages live in `@elevasis/ui/features/content`.
|
|
25
|
+
- **Headless hooks:** item, attempt, distribution, artifact, and pipeline-summary hooks live under `@elevasis/ui/hooks`.
|
|
26
|
+
- **Workflow adapter:** `content` from `@elevasis/sdk/worker` lets workflows create items, record attempts, and open distributions through platform tools.
|
|
27
|
+
- **Rules documents:** governing documents (idea banks, ICP docs, style guides, selection briefs) are **artifacts**, reached through the `artifacts` adapter, not a content-specific table.
|
|
28
|
+
- **System API Interface:** `content` uses the flat `system.apiInterface` marker. Readiness is derived from scoped resources, ontology bindings, and required catalogs — `content:object/item` plus the pipeline, step, and status catalogs.
|
|
29
|
+
|
|
30
|
+
## The one thing to understand first: a pipeline is data
|
|
31
|
+
|
|
32
|
+
Every other system in this scaffold has its structure in code. Content does not. A pipeline is a catalog record, its steps are a second catalog record, and a workflow claims a step **by step key**. Adding a pipeline is therefore an org-model edit plus one workflow per step — no changes to shared UI, no new routes, no fork.
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
content:catalog/pipeline which pipelines exist
|
|
36
|
+
content:catalog/{pipeline}-steps the ordered steps of one pipeline
|
|
37
|
+
content:catalog/status item lifecycle statuses
|
|
38
|
+
content:catalog/pillar editorial pillars (optional)
|
|
39
|
+
content:catalog/platform distribution targets
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
A pipeline's step catalog is named `content:catalog/{pipelineId}-steps` and referenced from the pipeline record's `stepCatalog`. Get that name wrong and the workspace renders an empty pipeline with no error — the binding is by string.
|
|
43
|
+
|
|
44
|
+
## Decision Table
|
|
45
|
+
|
|
46
|
+
| User wants | Start here | Notes |
|
|
47
|
+
| ------------------------------------------------- | ---------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
|
|
48
|
+
| Add a pipeline, step, status, pillar, or platform | `core/config/organization-model.ts` — the content System's `ontology` | Catalog edit. Add the step catalog **and** point the pipeline record's `stepCatalog` at it. |
|
|
49
|
+
| Produce the next step of an item from a workflow | `content` from `@elevasis/sdk/worker` | One workflow per step. Claim the step with `stepKey`; return the next step key. |
|
|
50
|
+
| Change the review screen | `ContentItemReviewPage` slots, or compose `ContentReviewCard` yourself | Prefer slots. The card, action bar, alternates panel, and processing strip are all exported individually. |
|
|
51
|
+
| Add content nav or a content route | `CONTENT_ITEMS`, `ContentSidebar`, `ContentSidebarMiddle` | Same manifest/sidebar composition pattern as CRM and lead-gen. |
|
|
52
|
+
| Store a governing document producers should read | `artifacts` from `@elevasis/sdk/worker` | `kind` MUST start with `content:` or RLS rejects an org-owned write. Pass `pipelineId` or `getActive` never finds it. |
|
|
53
|
+
| Track where a piece was published | `createDistribution` / `updateDistribution` | One distribution row per platform per item. |
|
|
54
|
+
| Add a new persisted content column or table | Platform/API migration work, not just scaffold work | DB, core schemas/types, API service/handlers, hooks, docs, and scaffold contracts move together. |
|
|
55
|
+
|
|
56
|
+
## Published Content Surfaces
|
|
57
|
+
|
|
58
|
+
| Surface | Import from | Use for |
|
|
59
|
+
| -------------------------------------------------------------------------------------------------------------------------- | ------------------------------- | ------------------------------------------------- |
|
|
60
|
+
| `contentManifest`, `CONTENT_ITEMS`, `ContentSidebar`, `ContentSidebarTop`, `ContentSidebarMiddle`, `MyReviewQueuePanel` | `@elevasis/ui/features/content` | Feature registration and sidebar composition |
|
|
61
|
+
| `ContentOverviewPage`, `ContentItemsPage`, `ContentItemReviewPage`, `ContentPipelinesPage`, `ContentPipelineWorkspacePage` | `@elevasis/ui/features/content` | Shared pages you can route to or wrap |
|
|
62
|
+
| `ContentSettingsPage`, `ContentDistributionsPage`, `ContentDistributionDetailPage` | `@elevasis/ui/features/content` | Rules-and-artifacts and distribution surfaces |
|
|
63
|
+
| `ContentReviewCard`, `ReviewActionBar`, `PayloadBody`, `AlternatesPanel`, `ProcessingStateStrip` | `@elevasis/ui/features/content` | Review UI primitives for a custom review screen |
|
|
64
|
+
| `useContentConfig`, `resolveContentStepResource` | `@elevasis/ui/features/content` | Read the resolved pipeline/step catalogs from OM |
|
|
65
|
+
| `useContentItems`, `useContentItem`, `useContentItemAttempts`, `useUpdateContentItem`, `useReviewContentItem` | `@elevasis/ui/hooks` | Item data access and review mutations |
|
|
66
|
+
| `useContentDistributions`, `useContentDistribution`, `useUpdateContentDistribution` | `@elevasis/ui/hooks` | Distribution data access |
|
|
67
|
+
| `useContentArtifacts`, `filterContentArtifacts`, `useContentPipelineSummary` | `@elevasis/ui/hooks` | Rules documents and pipeline roll-ups |
|
|
68
|
+
| `content`, `artifacts` | `@elevasis/sdk/worker` | Workflow-side content and rules-document adapters |
|
|
69
|
+
|
|
70
|
+
Read the generated contracts before changing typed boundaries:
|
|
71
|
+
|
|
72
|
+
`operations/node_modules/@elevasis/sdk/reference/scaffold/reference/contracts.md`
|
|
73
|
+
|
|
74
|
+
Look for the **Content Platform Primitives** section: item/attempt/distribution rows, review decisions, the status and pillar vocabularies, and the `ContentToolMap` adapter map.
|
|
75
|
+
|
|
76
|
+
## 1. Add a Pipeline
|
|
77
|
+
|
|
78
|
+
A pipeline is two catalog records plus the workflows that claim its steps.
|
|
79
|
+
|
|
80
|
+
<!-- doc-snippet:skip: illustrative excerpt -- catalog shape, not a compilable module -->
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
// core/config/organization-model.ts -- inside the content System's `ontology.catalogTypes`
|
|
84
|
+
'content:catalog/pipeline': {
|
|
85
|
+
entries: {
|
|
86
|
+
'podcast-to-clips': {
|
|
87
|
+
label: 'Podcast to Clips',
|
|
88
|
+
stepCatalog: 'content:catalog/podcast-to-clips-steps'
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
'content:catalog/podcast-to-clips-steps': {
|
|
93
|
+
entries: {
|
|
94
|
+
'transcript': { label: 'Transcript', order: 1 },
|
|
95
|
+
'clip-selection': { label: 'Clip Selection', order: 2 },
|
|
96
|
+
'publish': { label: 'Publish', order: 3 }
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Then set `content.config.defaultPipelineId` if this should be the pipeline the UI opens first.
|
|
102
|
+
|
|
103
|
+
**The step catalog name is load-bearing.** `content:catalog/{pipelineId}-steps` must match the pipeline record's `stepCatalog` exactly. Nothing type-checks that string.
|
|
104
|
+
|
|
105
|
+
## 2. Produce a Step in a Workflow
|
|
106
|
+
|
|
107
|
+
One workflow per step. The workflow records an **attempt** against the item, tagged with the step it satisfies:
|
|
108
|
+
|
|
109
|
+
<!-- doc-snippet:skip: illustrative excerpt -- `resourceDescriptors` names the project's own resource-descriptors export -->
|
|
110
|
+
|
|
111
|
+
```ts
|
|
112
|
+
// operations/src/content/clip-selection.ts
|
|
113
|
+
import type { WorkflowDefinition } from '@elevasis/sdk'
|
|
114
|
+
import { content } from '@elevasis/sdk/worker'
|
|
115
|
+
import { resourceDescriptors } from '@core/config/organization-model'
|
|
116
|
+
import { z } from 'zod'
|
|
117
|
+
|
|
118
|
+
const inputSchema = z.object({ contentItemId: z.string().uuid() })
|
|
119
|
+
const outputSchema = z.object({ attemptId: z.string(), nextStepKey: z.string().nullable() })
|
|
120
|
+
|
|
121
|
+
export const clipSelectionWorkflow: WorkflowDefinition = {
|
|
122
|
+
config: {
|
|
123
|
+
resource: resourceDescriptors.clipSelection,
|
|
124
|
+
resourceId: resourceDescriptors.clipSelection.id,
|
|
125
|
+
name: 'Clip Selection',
|
|
126
|
+
description: 'Chooses clip candidates for a podcast episode.',
|
|
127
|
+
type: resourceDescriptors.clipSelection.kind,
|
|
128
|
+
version: '1.0.0',
|
|
129
|
+
status: 'dev'
|
|
130
|
+
},
|
|
131
|
+
contract: { inputSchema, outputSchema },
|
|
132
|
+
steps: {
|
|
133
|
+
select: {
|
|
134
|
+
id: 'select',
|
|
135
|
+
name: 'Select Clips',
|
|
136
|
+
description: 'Records a clip-selection attempt against the item.',
|
|
137
|
+
inputSchema,
|
|
138
|
+
outputSchema,
|
|
139
|
+
next: null,
|
|
140
|
+
handler: async (rawInput, context) => {
|
|
141
|
+
const input = rawInput as z.infer<typeof inputSchema>
|
|
142
|
+
const item = await content.getItem({ contentItemId: input.contentItemId })
|
|
143
|
+
|
|
144
|
+
const attempt = await content.createAttempt({
|
|
145
|
+
contentItemId: item.id,
|
|
146
|
+
// Tags the attempt with the step it satisfies. Omit it and the
|
|
147
|
+
// attempt is stored with a null step -- the review UI's Step column
|
|
148
|
+
// renders "--" and nothing tells you which step produced the row.
|
|
149
|
+
stepKey: 'clip-selection',
|
|
150
|
+
payload: { clips: [] },
|
|
151
|
+
sourceExecutionId: context.executionId
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
return { attemptId: attempt.id, nextStepKey: 'publish' }
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
entryPoint: 'select'
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
`ContentToolMap` is the full surface: `createItem`, `getItem`, `listItems`, `updateItem`, `createAttempt`, `listAttempts`, `createDistribution`, `updateDistribution`. `organizationId` is injected server-side — never pass it from workflow code.
|
|
163
|
+
|
|
164
|
+
## 3. Store Rules Documents
|
|
165
|
+
|
|
166
|
+
Governing documents are artifacts, not a content table. Two rules decide whether a write is reachable at all:
|
|
167
|
+
|
|
168
|
+
```ts
|
|
169
|
+
import { artifacts } from '@elevasis/sdk/worker'
|
|
170
|
+
|
|
171
|
+
// `kind` MUST be `content:`-prefixed. The artifacts_manage_content RLS policy
|
|
172
|
+
// grants org-owned writes only for `kind LIKE 'content:%'` -- a non-prefixed
|
|
173
|
+
// kind is rejected server-side with no compile-time warning.
|
|
174
|
+
//
|
|
175
|
+
// `pipelineId` MUST be supplied. getActive filters on
|
|
176
|
+
// (organizationId, pipelineId, kind); an artifact written without one can
|
|
177
|
+
// never be read back by the lookup every consumer uses.
|
|
178
|
+
await artifacts.createArtifact({
|
|
179
|
+
ownerKind: 'organization',
|
|
180
|
+
ownerId: null,
|
|
181
|
+
kind: 'content:style-guide',
|
|
182
|
+
pipelineId: 'podcast-to-clips',
|
|
183
|
+
content: { tone: 'direct', bannedPhrases: ['synergy'] }
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
const { artifact } = await artifacts.getActive({
|
|
187
|
+
pipelineId: 'podcast-to-clips',
|
|
188
|
+
kind: 'content:style-guide'
|
|
189
|
+
})
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
Writing again for the same `(pipelineId, kind)` supersedes the previous version rather than colliding: the platform deactivates the predecessor and stores the new row at the next version number, atomically. `getActive` always returns the current one, and `ContentSettingsPage` renders the version history.
|
|
193
|
+
|
|
194
|
+
`ContentSettingsPage` scopes its browser to **declared** pipelines. An artifact written under a `pipelineId` that is not in `content:catalog/pipeline` is stored correctly and is invisible in that UI.
|
|
195
|
+
|
|
196
|
+
## 4. Customize the Review Screen
|
|
197
|
+
|
|
198
|
+
Start with slots on the shared page:
|
|
199
|
+
|
|
200
|
+
```tsx
|
|
201
|
+
// ui/src/routes/content/items.$itemId.tsx
|
|
202
|
+
import { createFileRoute } from '@tanstack/react-router'
|
|
203
|
+
import { ContentItemReviewPage } from '@elevasis/ui/features/content'
|
|
204
|
+
import { Button } from '@mantine/core'
|
|
205
|
+
|
|
206
|
+
export const Route = createFileRoute('/content/items/$itemId')({ component: ReviewRoute })
|
|
207
|
+
|
|
208
|
+
function ReviewRoute() {
|
|
209
|
+
const { itemId } = Route.useParams()
|
|
210
|
+
return <ContentItemReviewPage itemId={itemId} headerActions={<Button>Send to Legal</Button>} />
|
|
211
|
+
}
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
When the layout itself has to change, compose the primitives — `ContentReviewCard`, `ReviewActionBar`, `PayloadBody`, `AlternatesPanel`, `ProcessingStateStrip` — with `useContentItem`, `useContentItemAttempts`, and `useReviewContentItem`. Read the pipeline's steps through `useContentConfig` rather than hardcoding them, or a new step silently fails to render.
|
|
215
|
+
|
|
216
|
+
## 5. Extend Navigation
|
|
217
|
+
|
|
218
|
+
Same pattern as the other systems:
|
|
219
|
+
|
|
220
|
+
```tsx
|
|
221
|
+
import { contentManifest, CONTENT_ITEMS, ContentSidebar, ContentSidebarMiddle } from '@elevasis/ui/features/content'
|
|
222
|
+
import type { SystemModule } from '@elevasis/ui/provider'
|
|
223
|
+
import type { NavItem } from '@elevasis/ui/layout'
|
|
224
|
+
import { IconChartBar } from '@tabler/icons-react'
|
|
225
|
+
|
|
226
|
+
const customItems: NavItem[] = [
|
|
227
|
+
...CONTENT_ITEMS,
|
|
228
|
+
{ label: 'Performance', to: '/content/performance', icon: IconChartBar, exact: false }
|
|
229
|
+
]
|
|
230
|
+
|
|
231
|
+
export const customContentManifest: SystemModule = {
|
|
232
|
+
...contentManifest,
|
|
233
|
+
sidebar: () => (
|
|
234
|
+
<ContentSidebar>
|
|
235
|
+
<ContentSidebarMiddle items={customItems} />
|
|
236
|
+
</ContentSidebar>
|
|
237
|
+
)
|
|
238
|
+
}
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
Replace `contentManifest` with `customContentManifest` in the local `SYSTEM_MANIFESTS` array and add the matching route under `ui/src/routes/content/`.
|
|
242
|
+
|
|
243
|
+
## 6. Mark the System API-Ready
|
|
244
|
+
|
|
245
|
+
`content.apiInterface.lifecycle` goes `active` only when the System's scoped resources, `content:object/item`, and the pipeline/step/status catalogs are all in place. Two failure modes are worth knowing before you flip it:
|
|
246
|
+
|
|
247
|
+
- An **empty** `apiInterface.resourceIds` array throws rather than opting out. Omitting the whole `apiInterface` block is the opt-out; a `resourceIds: []` stub is the anomaly.
|
|
248
|
+
- Readiness is derived from a **deployed** snapshot. A model edit that has not been redeployed produces a stale-snapshot 503 that looks like a code bug. Redeploy before debugging.
|
|
249
|
+
|
|
250
|
+
## Verify
|
|
251
|
+
|
|
252
|
+
Run the checks for the surfaces you touched:
|
|
253
|
+
|
|
254
|
+
```bash
|
|
255
|
+
pnpm -C ui run check-types
|
|
256
|
+
pnpm -C operations run check
|
|
257
|
+
pnpm -C operations exec elevasis-sdk check
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
If you changed platform-level content contracts in the monorepo, the platform maintainer must also regenerate and verify scaffold output:
|
|
261
|
+
|
|
262
|
+
```bash
|
|
263
|
+
pnpm scaffold:sync
|
|
264
|
+
pnpm scaffold:verify
|
|
265
|
+
```
|
|
@@ -349,24 +349,26 @@ Use `useArtifacts({ ownerKind, ownerId })` for durable JSON artifacts like audit
|
|
|
349
349
|
|
|
350
350
|
Inside deployed workflows, use worker adapters instead of browser hooks or direct database access:
|
|
351
351
|
|
|
352
|
-
External projects should define workflow input/output schemas in `@
|
|
352
|
+
External projects should define reusable workflow input/output schemas in `@core/types` (the `@core/*` tsconfig path resolves to the project's own `core/` directory); the example below assumes those shared schemas already exist. There is no `@shared/*` alias in a template-family project — do not write one.
|
|
353
353
|
|
|
354
|
-
|
|
354
|
+
**Do not hand-bind the OM Resource descriptor on each workflow.** `projectDeploymentSpec` in `operations/src/index.ts` takes a `getWorkflowResourceDescriptor` resolver and binds `config.resource`, `config.resourceId`, and `config.type` for every workflow centrally. It looks the descriptor up by the `config.resourceId` you author and then **overwrites `resource` and `type` unconditionally** — so setting them per workflow is dead code that reads as a contract and drifts from the model without any error. Author `resourceId` and let the spec bind the rest.
|
|
355
|
+
|
|
356
|
+
<!-- doc-snippet:skip: illustrative excerpt -- `@core/types` resolves to the project's own `core/types/` module via the `@core/*` tsconfig path, `resourceDescriptors` names the project's own resource-descriptors export, and `z` (zod) is shown unimported -->
|
|
355
357
|
|
|
356
358
|
```ts
|
|
357
359
|
// operations/src/sales/qualify-list.ts
|
|
358
360
|
import type { WorkflowDefinition } from '@elevasis/sdk'
|
|
359
361
|
import { acqDb, list } from '@elevasis/sdk/worker'
|
|
360
|
-
import { qualifyListInputSchema, qualifyListOutputSchema } from '@
|
|
362
|
+
import { qualifyListInputSchema, qualifyListOutputSchema } from '@core/types'
|
|
361
363
|
import { resourceDescriptors } from '@core/config/organization-model'
|
|
362
364
|
|
|
363
365
|
export const qualifyListWorkflow: WorkflowDefinition = {
|
|
364
366
|
config: {
|
|
365
|
-
|
|
367
|
+
// `resourceId` is the only descriptor field you author -- it is the lookup key
|
|
368
|
+
// `projectDeploymentSpec` resolves against your OM. See the note below.
|
|
366
369
|
resourceId: resourceDescriptors.qualifyList.id,
|
|
367
370
|
name: 'Qualify List',
|
|
368
371
|
description: 'Marks list contacts as verified based on qualification checks.',
|
|
369
|
-
type: resourceDescriptors.qualifyList.kind,
|
|
370
372
|
version: '1.0.0',
|
|
371
373
|
status: 'dev',
|
|
372
374
|
},
|
|
@@ -458,14 +460,10 @@ Practical consequence: you can rename systems, restructure ontology, swap export
|
|
|
458
460
|
|
|
459
461
|
### Declaring an OM Readiness Contract
|
|
460
462
|
|
|
461
|
-
When you define a custom `readinessProfile` id (any string that is not one of the built-in presets `sales.lead-gen.api`, `sales.crm.api`, `sales.lead-gen.crm-handoff`), you must also provide a `readinessContract` on the `apiInterface` so the platform can validate structural readiness without hardcoded constants.
|
|
463
|
+
When you define a custom `readinessProfile` id (any string that is not one of the built-in presets `sales.lead-gen.api`, `sales.crm.api`, `sales.lead-gen.crm-handoff`), you must also provide a `readinessContract` on the `apiInterface` so the platform can validate structural readiness without hardcoded constants. This is mandatory — there is no `registerReadinessProfile` call that lets a custom system skip declaring a `readinessContract`. `registerReadinessProfile` (below) only records that an id has been chosen; it registers no validation logic and does not change how `computeInterfaceReadiness` treats the system, so it is optional and has no effect on whether the contract is required.
|
|
462
464
|
|
|
463
465
|
```ts
|
|
464
466
|
// core/config/organization-model.ts
|
|
465
|
-
import { registerReadinessProfile } from '@elevasis/sdk'
|
|
466
|
-
|
|
467
|
-
// Register the profile id at startup so the platform recognizes it
|
|
468
|
-
registerReadinessProfile('acme.prospecting.api')
|
|
469
467
|
|
|
470
468
|
export const organizationModel = {
|
|
471
469
|
// ...
|
|
@@ -497,30 +495,30 @@ export const organizationModel = {
|
|
|
497
495
|
|
|
498
496
|
`readinessContract.requiredObjects` and `requiredCatalogs` are arrays of ontology IDs owned by this system. The platform validates that each declared object type exists in the OM and that each declared catalog type has at least one entry. `requiredCatalogs` must declare at least one entry — an empty contract asserts nothing and is rejected.
|
|
499
497
|
|
|
500
|
-
Built-in profile ids (`sales.lead-gen.api`, etc.) derive their requirements from platform code.
|
|
498
|
+
Built-in profile ids (`sales.lead-gen.api`, etc.) derive their requirements from platform code. **A `readinessContract` declared alongside a built-in profile is ignored outright — not merely unnecessary.** The readiness engine branches on the profile: a registered built-in runs its own validator, and the authored contract is read only on the `else` branch, so anything you declare next to a built-in profile validates nothing and silently drifts from what the platform actually enforces. Real projects have shipped full contracts this way believing they were enforced.
|
|
499
|
+
|
|
500
|
+
Note that the determinant is the **profile**, not the system. A custom system using a built-in profile still gets the built-in's requirements and still ignores an authored contract; a custom profile id is what makes `readinessContract` required (and it is required — a custom profile without one fails readiness with `missing-readiness-contract`).
|
|
501
501
|
|
|
502
|
-
The `lookupReadinessProfile`, `registerReadinessProfile`, and `isBuiltInReadinessProfile` functions are exported from `@elevasis/sdk` alongside the `SystemApiInterfaceReadinessContract`, `ReadinessProfileKind`, and `ReadinessProfileEntry` types.
|
|
502
|
+
The `lookupReadinessProfile`, `registerReadinessProfile`, and `isBuiltInReadinessProfile` functions are exported from `@elevasis/sdk` alongside the `SystemApiInterfaceReadinessContract`, `ReadinessProfileKind`, and `ReadinessProfileEntry` types. `registerReadinessProfile(profileId)` only acknowledges a custom id for introspection (`lookupReadinessProfile` then resolves it as `kind: 'custom'` instead of `undefined`) — it cannot make `isBuiltInReadinessProfile` return `true` and it is never a substitute for a `readinessContract`. The actual extension point for platform-code-driven readiness (no `readinessContract` needed) is `registerBuiltInReadinessProfile`, exported from `@elevasis/core/organization-model/readiness` — it takes real structural validation logic, not just an id.
|
|
503
503
|
|
|
504
504
|
### Export Channel as Tenant Business Config
|
|
505
505
|
|
|
506
506
|
The export channel — ClickUp, CSV/XLSX file, platform push, or a custom destination — is an action + credential choice declared in your tenant OM. The platform owns the list-builder workflow primitive and the file storage adapters; you own which workflow id handles export and which credential it uses.
|
|
507
507
|
|
|
508
|
-
<!-- doc-snippet:skip: illustrative excerpt -- `@
|
|
508
|
+
<!-- doc-snippet:skip: illustrative excerpt -- `@core/types` resolves to the project's own `core/types/` module via the `@core/*` tsconfig path, and `buildCsvFromContacts` is a tenant-authored helper this recipe does not define -->
|
|
509
509
|
|
|
510
510
|
```ts
|
|
511
511
|
// operations/src/sales/prospecting/export-list.ts
|
|
512
512
|
import type { WorkflowDefinition } from '@elevasis/sdk'
|
|
513
513
|
import { acqDb, list, storage } from '@elevasis/sdk/worker'
|
|
514
|
-
import { exportListInputSchema, exportListOutputSchema } from '@
|
|
514
|
+
import { exportListInputSchema, exportListOutputSchema } from '@core/types'
|
|
515
515
|
import { resourceDescriptors } from '@core/config/organization-model'
|
|
516
516
|
|
|
517
517
|
export const exportListWorkflow: WorkflowDefinition = {
|
|
518
518
|
config: {
|
|
519
|
-
resource: resourceDescriptors.exportList,
|
|
520
519
|
resourceId: resourceDescriptors.exportList.id,
|
|
521
520
|
name: 'Export List',
|
|
522
521
|
description: 'Exports list contacts to a CSV file in project storage.',
|
|
523
|
-
type: 'workflow',
|
|
524
522
|
version: '1.0.0',
|
|
525
523
|
status: 'prod'
|
|
526
524
|
},
|
|
@@ -31,6 +31,9 @@ You want to build on the shared CRM without forking it: add CRM routes, compose
|
|
|
31
31
|
**[Build and Extend Lead Gen](extend-lead-gen.md)**
|
|
32
32
|
You want to build on the shared lead-gen system without forking it: add lead-gen routes, compose sidebars/pages, use list/company/contact/artifact hooks, mutate list data from workflows, or understand which contracts and adapters form the extension surface.
|
|
33
33
|
|
|
34
|
+
**[Build and Extend Content](extend-content.md)**
|
|
35
|
+
You want to build on the shared content system without forking it: add a pipeline, produce a step from a workflow, customize the review screen, store rules documents, or track distributions. Covers the catalog-driven pipeline model (a pipeline is data, not code), the `content:catalog/{pipeline}-steps` binding, attempt authoring with `stepKey`, artifacts as versioned governing documents with their two silent write constraints, review-page slots and primitives, and the `apiInterface` readiness rules.
|
|
36
|
+
|
|
34
37
|
**[Customize CRM Actions](customize-crm-actions.md)**
|
|
35
38
|
You want to add, hide, or replace CRM deal action buttons, configure the shared `crmActions` provider path, or call a project-owned workflow from custom UI when server-side action dispatch constraints require it. Covers `ActionDef`, the caller-supplied `crmActions` catalog, provider wiring, and the current v1 boundary for custom action dispatch.
|
|
36
39
|
|
|
@@ -45,6 +48,6 @@ You want to browse, inspect, or traverse the OrganizationModel knowledge graph f
|
|
|
45
48
|
## Reference docs these recipes link into
|
|
46
49
|
|
|
47
50
|
- [glossary.md](../reference/glossary.md) -- term disambiguation for System, Resource, systemId, Topology, Settings asymmetry
|
|
48
|
-
- [contracts.md](../reference/contracts.md) -- TypeScript shapes: `SystemModule`, `OrganizationModel`, CRM deal types, lead-gen list/member/artifact types, `CrmToolMap`, `LeadToolMap`, `ListToolMap`, `ActionDef`
|
|
51
|
+
- [contracts.md](../reference/contracts.md) -- TypeScript shapes: `SystemModule`, `OrganizationModel`, CRM deal types, lead-gen list/member/artifact types, content item/attempt/distribution types, `CrmToolMap`, `LeadToolMap`, `ListToolMap`, `ContentToolMap`, `ActionDef`
|
|
49
52
|
- [feature-flags-and-gating.md](../ui/feature-flags-and-gating.md) -- full three-concept gating model
|
|
50
53
|
- [workflow-recipes.md](../operations/workflow-recipes.md) -- workflow anatomy, adapters, trigger patterns
|
|
@@ -402,13 +402,6 @@ export type OrganizationModelSystemApiInterface = z.infer<typeof SystemApiInterf
|
|
|
402
402
|
export type OrganizationModelSystemInterfaceRef = z.infer<typeof SystemInterfaceRefSchema>
|
|
403
403
|
```
|
|
404
404
|
|
|
405
|
-
### `OrganizationModelSystemStatus`
|
|
406
|
-
|
|
407
|
-
```typescript
|
|
408
|
-
/** @deprecated Use OrganizationModelSystemLifecycle. Accepted for one publish cycle. */
|
|
409
|
-
export type OrganizationModelSystemStatus = z.infer<typeof SystemStatusSchema>
|
|
410
|
-
```
|
|
411
|
-
|
|
412
405
|
### `OrganizationModelResources`
|
|
413
406
|
|
|
414
407
|
```typescript
|
|
@@ -651,48 +644,6 @@ export type OrganizationModelEntity = z.infer<typeof EntitySchema>
|
|
|
651
644
|
export type OrganizationModelEntities = z.infer<typeof EntitiesDomainSchema>
|
|
652
645
|
```
|
|
653
646
|
|
|
654
|
-
### `OrganizationModelPolicies`
|
|
655
|
-
|
|
656
|
-
```typescript
|
|
657
|
-
export type OrganizationModelPolicies = z.infer<typeof PoliciesDomainSchema>
|
|
658
|
-
```
|
|
659
|
-
|
|
660
|
-
### `OrganizationModelPolicy`
|
|
661
|
-
|
|
662
|
-
```typescript
|
|
663
|
-
export type OrganizationModelPolicy = z.infer<typeof PolicySchema>
|
|
664
|
-
```
|
|
665
|
-
|
|
666
|
-
### `OrganizationModelPolicyId`
|
|
667
|
-
|
|
668
|
-
```typescript
|
|
669
|
-
export type OrganizationModelPolicyId = z.infer<typeof PolicyIdSchema>
|
|
670
|
-
```
|
|
671
|
-
|
|
672
|
-
### `OrganizationModelPolicyApplicability`
|
|
673
|
-
|
|
674
|
-
```typescript
|
|
675
|
-
export type OrganizationModelPolicyApplicability = z.infer<typeof PolicyApplicabilitySchema>
|
|
676
|
-
```
|
|
677
|
-
|
|
678
|
-
### `OrganizationModelPolicyTrigger`
|
|
679
|
-
|
|
680
|
-
```typescript
|
|
681
|
-
export type OrganizationModelPolicyTrigger = z.infer<typeof PolicyTriggerSchema>
|
|
682
|
-
```
|
|
683
|
-
|
|
684
|
-
### `OrganizationModelPolicyPredicate`
|
|
685
|
-
|
|
686
|
-
```typescript
|
|
687
|
-
export type OrganizationModelPolicyPredicate = z.infer<typeof PolicyPredicateSchema>
|
|
688
|
-
```
|
|
689
|
-
|
|
690
|
-
### `OrganizationModelPolicyEffect`
|
|
691
|
-
|
|
692
|
-
```typescript
|
|
693
|
-
export type OrganizationModelPolicyEffect = z.infer<typeof PolicyEffectSchema>
|
|
694
|
-
```
|
|
695
|
-
|
|
696
647
|
### `OrganizationModelKnowledge`
|
|
697
648
|
|
|
698
649
|
```typescript
|
|
@@ -852,7 +803,6 @@ export interface ResolvedShellModel {
|
|
|
852
803
|
ancestorsOf: (id: string) => ResolvedShellSystem[]
|
|
853
804
|
parentOf: (id: string) => ResolvedShellSystem | undefined
|
|
854
805
|
topLevel: () => ResolvedShellSystem[]
|
|
855
|
-
requiresAdminFor: (id: string) => boolean
|
|
856
806
|
devOnlyFor: (id: string) => boolean
|
|
857
807
|
}
|
|
858
808
|
```
|
|
@@ -2925,7 +2875,7 @@ export const AcqContactListResponseSchema = z.object({
|
|
|
2925
2875
|
### `AcqArtifactOwnerKindSchema`
|
|
2926
2876
|
|
|
2927
2877
|
```typescript
|
|
2928
|
-
export const AcqArtifactOwnerKindSchema = z.enum(['company', 'contact', 'deal', 'list', 'list_member'])
|
|
2878
|
+
export const AcqArtifactOwnerKindSchema = z.enum(['company', 'contact', 'deal', 'list', 'list_member', 'organization'])
|
|
2929
2879
|
```
|
|
2930
2880
|
|
|
2931
2881
|
### `ListArtifactsQuerySchema`
|
|
@@ -2934,7 +2884,7 @@ export const AcqArtifactOwnerKindSchema = z.enum(['company', 'contact', 'deal',
|
|
|
2934
2884
|
export const ListArtifactsQuerySchema = z
|
|
2935
2885
|
.object({
|
|
2936
2886
|
ownerKind: AcqArtifactOwnerKindSchema,
|
|
2937
|
-
ownerId: UuidSchema
|
|
2887
|
+
ownerId: UuidSchema.optional()
|
|
2938
2888
|
})
|
|
2939
2889
|
.strict()
|
|
2940
2890
|
```
|
|
@@ -2945,10 +2895,17 @@ export const ListArtifactsQuerySchema = z
|
|
|
2945
2895
|
export const CreateArtifactRequestSchema = z
|
|
2946
2896
|
.object({
|
|
2947
2897
|
ownerKind: AcqArtifactOwnerKindSchema,
|
|
2948
|
-
|
|
2898
|
+
// Nullable + optional: an org-owned artifact (ownerKind: 'organization') has no
|
|
2899
|
+
// single owning entity row. See the comment on AcqArtifactOwnerKindSchema above.
|
|
2900
|
+
ownerId: UuidSchema.nullable().optional(),
|
|
2949
2901
|
kind: z.string().trim().min(1).max(255),
|
|
2950
2902
|
content: z.record(z.string(), z.unknown()),
|
|
2951
|
-
sourceExecutionId: UuidSchema.optional()
|
|
2903
|
+
sourceExecutionId: UuidSchema.optional(),
|
|
2904
|
+
// Nullable + optional: acquisition's own artifacts (audits, proposals, ICP docs) predate the
|
|
2905
|
+
// pipeline concept and have none. `getActiveArtifact` filters on it, so any artifact meant to
|
|
2906
|
+
// be resolvable as "the active document for pipeline X" MUST supply it -- artifacts-platform-tool
|
|
2907
|
+
// .mdx Step 3 defect 1.
|
|
2908
|
+
pipelineId: PipelineIdSchema.nullable().optional()
|
|
2952
2909
|
})
|
|
2953
2910
|
.strict()
|
|
2954
2911
|
```
|
|
@@ -2960,12 +2917,18 @@ export const AcqArtifactResponseSchema = z.object({
|
|
|
2960
2917
|
id: z.string(),
|
|
2961
2918
|
organizationId: z.string(),
|
|
2962
2919
|
ownerKind: z.string(),
|
|
2963
|
-
|
|
2920
|
+
// Nullable as of content-pipeline-foundation D14: an org-owned artifact has no owning entity row.
|
|
2921
|
+
// CreateArtifactRequestSchema already accepted a null ownerId; this response schema not doing so
|
|
2922
|
+
// was Step 3 defect 2 -- an org-owned artifact inserted fine and then failed serialization.
|
|
2923
|
+
ownerId: z.string().nullable(),
|
|
2964
2924
|
kind: z.string(),
|
|
2925
|
+
pipelineId: z.string().nullable(),
|
|
2965
2926
|
content: z.record(z.string(), z.unknown()),
|
|
2966
2927
|
sourceExecutionId: z.string().nullable(),
|
|
2967
2928
|
createdBy: z.string().nullable(),
|
|
2968
2929
|
createdAt: z.string(),
|
|
2930
|
+
updatedAt: z.string(),
|
|
2931
|
+
isActive: z.boolean(),
|
|
2969
2932
|
version: z.number().int()
|
|
2970
2933
|
})
|
|
2971
2934
|
```
|
|
@@ -14,6 +14,8 @@ description: Auto-generated catalog of registered system manifests. Do not edit
|
|
|
14
14
|
|
|
15
15
|
| System Key | System ID | Nav Label | Domains | Routes |
|
|
16
16
|
| --- | --- | --- | --- | --- |
|
|
17
|
+
| `clients` | `client-management` | — | — | — |
|
|
18
|
+
| `content` | `content` | — | — | — |
|
|
17
19
|
| `crm` | `sales.crm` | — | — | — |
|
|
18
20
|
| `delivery` | `platform.projects` | — | — | — |
|
|
19
21
|
| `knowledge` | `—` | — | — | — |
|
|
@@ -22,5 +24,6 @@ description: Auto-generated catalog of registered system manifests. Do not edit
|
|
|
22
24
|
| `operations` | `—` | — | — | — |
|
|
23
25
|
| `seo` | `seo` | — | — | — |
|
|
24
26
|
| `settings` | `—` | — | — | — |
|
|
27
|
+
| `submitted-requests` | `—` | — | — | — |
|
|
25
28
|
|
|
26
29
|
> **Note:** `auth` and `dashboard` are not in the registry — they are app-shell concerns, not gated systems.
|
|
@@ -64,7 +64,7 @@ description: Terminology disambiguation for Organization OS concepts used in the
|
|
|
64
64
|
|
|
65
65
|
**Settings asymmetry** -- Settings is a navigation/app surface whose individual pages are normally governed by admin checks rather than user-facing System toggles.
|
|
66
66
|
|
|
67
|
-
**Shell model** -- provider output used by sidebars and breadcrumbs. Includes `systems`, `childrenOf`, `ancestorsOf`, `parentOf`, `topLevel`, `findByPath`,
|
|
67
|
+
**Shell model** -- provider output used by sidebars and breadcrumbs. Includes `systems`, `childrenOf`, `ancestorsOf`, `parentOf`, `topLevel`, `findByPath`, and `devOnlyFor`.
|
|
68
68
|
|
|
69
69
|
**Subshell / Sidebar** -- System- or route-prefix-scoped UI region rendered when the current route matches a module whose manifest supplies a sidebar.
|
|
70
70
|
|
|
@@ -229,9 +229,9 @@ const MyDeliverySidebar = () => <ProjectsSidebar><MyDeliveryMiddle /></ProjectsS
|
|
|
229
229
|
|
|
230
230
|
This is the same compose-from-primitives path CRM consumers use for structural changes. No special API -- just `SubshellSidebarSection` + `SubshellNavList` from `@elevasis/ui/layout`.
|
|
231
231
|
|
|
232
|
-
For broader CRM extension work across pages, hooks, actions, workflows, and org-model boundaries, start with `operations/node_modules/@elevasis/sdk/reference/scaffold/recipes/extend-crm.md
|
|
232
|
+
For broader CRM extension work across pages, hooks, actions, workflows, and org-model boundaries, start with the `extend-crm.md` recipe. Internally, read the workspace source at `packages/sdk/docs/scaffold/recipes/extend-crm.md`; a tenant reads the published mirror at `operations/node_modules/@elevasis/sdk/reference/scaffold/recipes/extend-crm.md`, which is a copy made at the last publish and can lag the workspace.
|
|
233
233
|
|
|
234
|
-
For broader lead-gen extension work across pages, hooks, list/member state, artifacts, workflows, and org-model boundaries,
|
|
234
|
+
For broader lead-gen extension work across pages, hooks, list/member state, artifacts, workflows, and org-model boundaries, the same pair applies: `packages/sdk/docs/scaffold/recipes/extend-lead-gen.md` internally, `operations/node_modules/@elevasis/sdk/reference/scaffold/recipes/extend-lead-gen.md` for a tenant.
|
|
235
235
|
|
|
236
236
|
## Imports Cheat Sheet
|
|
237
237
|
|
|
@@ -56,8 +56,6 @@ interface OrganizationGraphSystemBridge {
|
|
|
56
56
|
ancestorsOf,
|
|
57
57
|
parentOf,
|
|
58
58
|
topLevel,
|
|
59
|
-
uiPositionFor,
|
|
60
|
-
requiresAdminFor,
|
|
61
59
|
devOnlyFor
|
|
62
60
|
}
|
|
63
61
|
```
|
|
@@ -66,7 +64,7 @@ Sidebar rendering walks `topLevel()` and `childrenOf(id)`. Breadcrumbs resolve `
|
|
|
66
64
|
|
|
67
65
|
## Access
|
|
68
66
|
|
|
69
|
-
System access is keyed by Organization Model system ID. `
|
|
67
|
+
System access is keyed by Organization Model system ID. A System is hidden from non-dev viewers when it, or any ancestor, is `lifecycle: 'beta'` — that is what `devOnlyFor(id)` reports. Systems carry no `devOnly` or `requiresAdmin` flags of their own; the `devOnly` and `requiresAdmin` fields on a **sidebar surface node** are separate, still authored, and still applied when deriving visible entries.
|
|
70
68
|
|
|
71
69
|
Routes still need guards:
|
|
72
70
|
|