@elevasis/sdk 1.5.3 → 1.5.4
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 +899 -57
- package/dist/index.d.ts +94 -110
- package/package.json +3 -3
- package/reference/_navigation.md +11 -1
- package/reference/_reference-manifest.json +70 -0
- package/reference/claude-config/commands/submit-issue.md +12 -0
- package/reference/claude-config/hooks/post-edit-validate.mjs +109 -0
- package/reference/claude-config/hooks/tool-failure-recovery.mjs +73 -0
- package/reference/claude-config/rules/deployment.md +57 -0
- package/reference/claude-config/rules/docs.md +26 -0
- package/reference/claude-config/rules/error-handling.md +56 -0
- package/reference/claude-config/rules/execution.md +40 -0
- package/reference/claude-config/rules/frontend.md +43 -0
- package/reference/claude-config/rules/observability.md +31 -0
- package/reference/claude-config/rules/organization-os.md +62 -0
- package/reference/claude-config/rules/platform.md +41 -0
- package/reference/claude-config/rules/shared-types.md +46 -0
- package/reference/claude-config/rules/task-tracking.md +47 -0
- package/reference/claude-config/scripts/statusline-command.js +18 -0
- package/reference/claude-config/settings.json +30 -0
- package/reference/claude-config/skills/deploy/SKILL.md +166 -0
- package/reference/claude-config/skills/dsp/SKILL.md +66 -0
- package/reference/claude-config/skills/elevasis/SKILL.md +239 -0
- package/reference/claude-config/skills/explore/SKILL.md +78 -0
- package/reference/claude-config/skills/project/SKILL.md +918 -0
- package/reference/claude-config/skills/save/SKILL.md +197 -0
- package/reference/claude-config/skills/setup/SKILL.md +210 -0
- package/reference/claude-config/skills/status/SKILL.md +60 -0
- package/reference/claude-config/skills/submit-issue/SKILL.md +179 -0
- package/reference/claude-config/skills/sync/SKILL.md +81 -0
- package/reference/cli.mdx +19 -4
- package/reference/deployment/provided-features.mdx +24 -2
- package/reference/framework/agent.mdx +12 -4
- package/reference/framework/project-structure.mdx +9 -3
- package/reference/packages/core/src/README.md +1 -1
- package/reference/packages/core/src/business/README.md +52 -0
- package/reference/packages/core/src/organization-model/README.md +25 -26
- package/reference/packages/ui/src/app/README.md +24 -0
- package/reference/platform-tools/type-safety.mdx +0 -10
- package/reference/scaffold/core/organization-graph.mdx +37 -28
- package/reference/scaffold/core/organization-model.mdx +34 -36
- package/reference/scaffold/index.mdx +1 -0
- package/reference/scaffold/operations/propagation-pipeline.md +7 -3
- package/reference/scaffold/operations/scaffold-maintenance.md +2 -2
- package/reference/scaffold/operations/workflow-recipes.md +18 -1
- package/reference/scaffold/recipes/add-a-feature.md +37 -21
- package/reference/scaffold/recipes/add-a-resource.md +4 -2
- package/reference/scaffold/recipes/customize-organization-model.md +400 -0
- package/reference/scaffold/recipes/extend-a-base-entity.md +140 -0
- package/reference/scaffold/recipes/gate-by-feature-or-admin.md +18 -12
- package/reference/scaffold/recipes/index.md +3 -3
- package/reference/scaffold/reference/contracts.md +11 -32
- package/reference/scaffold/reference/feature-registry.md +10 -9
- package/reference/scaffold/reference/glossary.md +14 -18
- package/reference/scaffold/ui/customization.md +2 -2
- package/reference/scaffold/ui/feature-flags-and-gating.md +40 -54
- package/reference/scaffold/ui/feature-shell.mdx +22 -23
- package/reference/scaffold/ui/recipes.md +118 -3
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Customize organization-model.ts
|
|
3
|
+
description: Annotated walkthrough for customizing the organization model in template-derived projects, covering the unified feature API, surfaces, domain config, and the resolve pipeline.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Customize organization-model.ts
|
|
7
|
+
|
|
8
|
+
`foundations/config/organization-model.ts` is the semantic contract between your UI runtime
|
|
9
|
+
and your platform operations. Every feature your users can access, every nav surface they can
|
|
10
|
+
navigate to, and every resource backed by a workflow is declared here. The provider reads this
|
|
11
|
+
contract at startup; everything downstream -- routing, gating, nav rendering -- derives from it.
|
|
12
|
+
|
|
13
|
+
This recipe walks through the file section by section. For adding a net-new feature from scratch
|
|
14
|
+
(manifest, routes, gating) see [add-a-feature.md](add-a-feature.md).
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Anatomy
|
|
19
|
+
|
|
20
|
+
### Import and setup
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
import {
|
|
24
|
+
defineOrganizationModel,
|
|
25
|
+
resolveOrganizationModel,
|
|
26
|
+
type OrganizationModel,
|
|
27
|
+
type OrganizationModelSurface
|
|
28
|
+
} from '@elevasis/core/organization-model'
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Two functions and two types are all you need:
|
|
32
|
+
|
|
33
|
+
- `defineOrganizationModel` -- identity helper that gives TypeScript a typed override shape.
|
|
34
|
+
Pass your override object through it; you get type-checking against `DeepPartial<OrganizationModel>`
|
|
35
|
+
without having to fill every field.
|
|
36
|
+
- `resolveOrganizationModel` -- merges your override with the platform defaults and validates the
|
|
37
|
+
result through `OrganizationModelSchema`. Call once at module init; export the result.
|
|
38
|
+
- `OrganizationModel` -- the fully-resolved model type (after defaults are merged in).
|
|
39
|
+
- `OrganizationModelSurface` -- the surface definition type; useful when building the foundation
|
|
40
|
+
navigation surface layer (see the export pattern below).
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
### Features array
|
|
45
|
+
|
|
46
|
+
The `features` array is the sole source of truth for which capabilities the organization has
|
|
47
|
+
and whether they are on or off. Each entry is an `OrganizationModelFeature` (inferred from
|
|
48
|
+
`FeatureSchema`).
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
const foundationOrganizationModelOverride = defineOrganizationModel({
|
|
52
|
+
features: [
|
|
53
|
+
{
|
|
54
|
+
id: 'crm', // stable kebab-case ID; FeatureModule.featureId must match
|
|
55
|
+
label: 'CRM', // display name for nav label resolution
|
|
56
|
+
description: 'Relationship pipeline and deal management', // optional; shown in settings UI
|
|
57
|
+
enabled: true, // org-wide default; false hides from nav and blocks routes
|
|
58
|
+
color: 'blue', // Mantine color token; optional
|
|
59
|
+
entityIds: ['crm.deal'], // logical entity types this feature owns
|
|
60
|
+
surfaceIds: ['crm.pipeline'], // navigation surfaces that belong to this feature
|
|
61
|
+
resourceIds: [], // workflow/agent resource IDs mapped through resourceMappings
|
|
62
|
+
capabilityIds: ['crm.pipeline.manage'] // fine-grained capability strings for gating
|
|
63
|
+
},
|
|
64
|
+
// ... other features
|
|
65
|
+
]
|
|
66
|
+
})
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Field reference:
|
|
70
|
+
|
|
71
|
+
- `id` -- lowercase, kebab-case, dot-separated segments allowed (`crm`, `lead-gen`, `operations`).
|
|
72
|
+
This value must exactly match the `featureId` on any `FeatureModule` manifest that gates against
|
|
73
|
+
this feature. The provider throws at startup if a manifest references an unknown feature ID.
|
|
74
|
+
- `label` -- shown in nav when no manifest override is provided. Keep short.
|
|
75
|
+
- `description` -- optional. Appears in membership/settings surfaces.
|
|
76
|
+
- `enabled` -- `false` suppresses the nav entry and triggers `FeatureGuard` redirects. Default is
|
|
77
|
+
`true` per the schema. To ship a feature but keep it off by default, set this to `false` here
|
|
78
|
+
and enable it per-org through membership config.
|
|
79
|
+
- `color` -- any Mantine color token string (`blue`, `cyan`, `violet`, `orange`). Optional.
|
|
80
|
+
- `icon` -- icon name string. Optional. The foundation layer re-maps this via `FoundationSurfaceIcon`.
|
|
81
|
+
- `entityIds` -- dot-namespaced entity type IDs (`crm.deal`, `leadgen.list`). Purely semantic; used
|
|
82
|
+
by the organization graph to classify nodes.
|
|
83
|
+
- `surfaceIds` -- must reference IDs declared in `navigation.surfaces`. Bidirectional: the schema
|
|
84
|
+
validates that every surface listed here also lists this feature in its own `featureIds`.
|
|
85
|
+
- `resourceIds` -- must reference `resourceId` values in `resourceMappings`. Bidirectional: the
|
|
86
|
+
schema validates the reverse link exists. Leave empty until you have actual resource mappings.
|
|
87
|
+
- `capabilityIds` -- dot-namespaced strings (`crm.pipeline.manage`). Used by `FeatureGuard` and
|
|
88
|
+
surface-level access checks. No central registry; name them descriptively.
|
|
89
|
+
|
|
90
|
+
The 7 platform defaults are: `crm`, `lead-gen`, `projects`, `operations`, `monitoring`, `settings`,
|
|
91
|
+
`seo`. The template's `features` array replaces the defaults wholesale (arrays are not merged field
|
|
92
|
+
by field -- see Resolve Behavior below).
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
### Navigation surfaces
|
|
97
|
+
|
|
98
|
+
Surfaces are the navigable pages of your application. Declare them under `navigation.surfaces`.
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
navigation: {
|
|
102
|
+
defaultSurfaceId: 'operations.organization-graph', // surface shown on first load
|
|
103
|
+
surfaces: [
|
|
104
|
+
{
|
|
105
|
+
id: 'crm.pipeline', // stable ID; referenced by feature.surfaceIds and group.surfaceIds
|
|
106
|
+
label: 'CRM', // nav label
|
|
107
|
+
path: '/crm', // TanStack Router path prefix
|
|
108
|
+
surfaceType: 'graph', // 'page' | 'dashboard' | 'graph' | 'detail' | 'list' | 'settings'
|
|
109
|
+
featureId: 'crm', // primary owning feature (optional but conventional)
|
|
110
|
+
featureIds: ['crm'], // all features this surface belongs to (bidirectional with feature.surfaceIds)
|
|
111
|
+
entityIds: ['crm.deal'], // entity types rendered on this surface
|
|
112
|
+
resourceIds: [], // resource IDs rendered on this surface (from resourceMappings)
|
|
113
|
+
capabilityIds: ['crm.pipeline.manage'] // capabilities required to access this surface
|
|
114
|
+
}
|
|
115
|
+
]
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Surface field notes:
|
|
120
|
+
|
|
121
|
+
- `surfaceType` -- drives how the organization graph classifies and renders the surface node.
|
|
122
|
+
Use `graph` for pipeline/topology views, `list` for index pages, `settings` for settings pages.
|
|
123
|
+
- `featureId` vs `featureIds` -- `featureId` is the single primary owner (optional). `featureIds`
|
|
124
|
+
is the bidirectional array that the schema validates against. In practice, set both to the same
|
|
125
|
+
single value for most surfaces.
|
|
126
|
+
- `parentId` -- optional. Lets you declare a surface as a child of another (for nested nav or
|
|
127
|
+
detail surfaces). Must reference a declared surface ID.
|
|
128
|
+
- Bidirectional constraint: if `crm.pipeline` is in `feature.surfaceIds`, then `crm.pipeline`'s
|
|
129
|
+
`featureIds` must include `crm`. The schema parse throws if either side is missing.
|
|
130
|
+
|
|
131
|
+
The template adds a `FoundationNavigationSurface` extension type that augments the core surface
|
|
132
|
+
with a typed `icon` field (`FoundationSurfaceIcon`). This is used by the foundation nav layer to
|
|
133
|
+
render icon components in the sidebar.
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
### Resources and resourceMappings
|
|
138
|
+
|
|
139
|
+
Resource mappings connect platform workflows and agents to features and surfaces. They are optional
|
|
140
|
+
until you have deployed resources to map.
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
resourceMappings: [
|
|
144
|
+
{
|
|
145
|
+
id: 'my-mapping', // unique within resourceMappings
|
|
146
|
+
resourceId: 'my-lead-scraper-workflow', // the deployed resource ID from operations/
|
|
147
|
+
resourceType: 'workflow', // 'workflow' | 'agent' | 'trigger' | 'integration' | 'external' | 'human_checkpoint'
|
|
148
|
+
label: 'Lead Scraper',
|
|
149
|
+
featureIds: ['lead-gen'], // bidirectional: feature.resourceIds must include resourceId
|
|
150
|
+
entityIds: ['leadgen.company'],
|
|
151
|
+
surfaceIds: ['lead-gen.lists'], // bidirectional: surface.resourceIds must include resourceId
|
|
152
|
+
capabilityIds: []
|
|
153
|
+
}
|
|
154
|
+
]
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
The schema enforces full bidirectionality. If you add a resource mapping with `featureIds: ['lead-gen']`
|
|
158
|
+
then the `lead-gen` feature entry must include the `resourceId` in its own `resourceIds`. Both sides
|
|
159
|
+
must be consistent or `resolveOrganizationModel` throws at startup. See [add-a-resource.md](add-a-resource.md)
|
|
160
|
+
for the full resource authoring workflow.
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
### Domain-specific config
|
|
165
|
+
|
|
166
|
+
The `crm`, `leadGen`, and `delivery` top-level keys configure pipeline stages, entity ID bindings,
|
|
167
|
+
and status vocabularies for those domains. These are consumed by the platform adapters and UI
|
|
168
|
+
components -- they are not just labels.
|
|
169
|
+
|
|
170
|
+
```ts
|
|
171
|
+
crm: {
|
|
172
|
+
entityId: 'crm.deal',
|
|
173
|
+
defaultPipelineId: 'default',
|
|
174
|
+
pipelines: [
|
|
175
|
+
{
|
|
176
|
+
id: 'default',
|
|
177
|
+
label: 'Default Pipeline',
|
|
178
|
+
entityId: 'crm.deal',
|
|
179
|
+
stages: [
|
|
180
|
+
{ id: 'interested', label: 'Interested', color: 'blue', order: 1, semanticClass: 'open', surfaceIds: ['crm.pipeline'], resourceIds: [] },
|
|
181
|
+
{ id: 'closed_won', label: 'Closed Won', color: 'green', order: 4, semanticClass: 'closed_won', surfaceIds: ['crm.pipeline'], resourceIds: [] }
|
|
182
|
+
// ... additional stages
|
|
183
|
+
]
|
|
184
|
+
}
|
|
185
|
+
]
|
|
186
|
+
},
|
|
187
|
+
leadGen: {
|
|
188
|
+
listEntityId: 'leadgen.list',
|
|
189
|
+
companyEntityId: 'leadgen.company',
|
|
190
|
+
contactEntityId: 'leadgen.contact',
|
|
191
|
+
companyStages: [
|
|
192
|
+
{ id: 'populated', label: 'Populated', order: 1 },
|
|
193
|
+
{ id: 'qualified', label: 'Qualified', order: 3 }
|
|
194
|
+
],
|
|
195
|
+
contactStages: [
|
|
196
|
+
{ id: 'discovered', label: 'Discovered', order: 1 },
|
|
197
|
+
{ id: 'uploaded', label: 'Uploaded', order: 4 }
|
|
198
|
+
]
|
|
199
|
+
},
|
|
200
|
+
delivery: {
|
|
201
|
+
projectEntityId: 'delivery.project',
|
|
202
|
+
milestoneEntityId: 'delivery.milestone',
|
|
203
|
+
taskEntityId: 'delivery.task',
|
|
204
|
+
projectStatuses: [ /* ... */ ],
|
|
205
|
+
milestoneStatuses: [ /* ... */ ],
|
|
206
|
+
taskStatuses: [ /* ... */ ]
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
`semanticClass` on CRM stages is significant: the platform uses `closed_won`, `closed_lost`,
|
|
211
|
+
`nurturing`, `open`, and `active` for funnel analytics and workflow triggers. Do not rename these
|
|
212
|
+
to arbitrary strings.
|
|
213
|
+
|
|
214
|
+
---
|
|
215
|
+
|
|
216
|
+
### Export pattern
|
|
217
|
+
|
|
218
|
+
The template exports two models: `canonicalOrganizationModel` for the provider (machine-facing)
|
|
219
|
+
and `organizationModel` for the foundation nav layer (UI-facing with the icon-augmented surface type).
|
|
220
|
+
|
|
221
|
+
```ts
|
|
222
|
+
const resolvedOrganizationModel = resolveOrganizationModel(foundationOrganizationModelOverride)
|
|
223
|
+
|
|
224
|
+
// Passed to ElevasisFeaturesProvider in ui/src/routes/__root.tsx
|
|
225
|
+
export const canonicalOrganizationModel: OrganizationModel = resolvedOrganizationModel
|
|
226
|
+
|
|
227
|
+
// Used by foundation nav components that need the augmented FoundationNavigationSurface type
|
|
228
|
+
export const organizationModel: FoundationOrganizationModel = {
|
|
229
|
+
...canonicalOrganizationModel,
|
|
230
|
+
navigation: {
|
|
231
|
+
defaultSurfaceId: 'operations',
|
|
232
|
+
homeLabel,
|
|
233
|
+
quickAccessSurfaceIds: [...quickAccessSurfaceIds],
|
|
234
|
+
surfaces: navigationSurfaces // FoundationNavigationSurface[] with icon field
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
The `requireCoreSurface` helper (defined in the template file) throws at startup if a surface ID
|
|
240
|
+
declared in the foundations layer is not present in the resolved model. Use it to catch mismatches
|
|
241
|
+
early rather than seeing silent `undefined` in the nav at runtime.
|
|
242
|
+
|
|
243
|
+
---
|
|
244
|
+
|
|
245
|
+
## Common Customizations
|
|
246
|
+
|
|
247
|
+
### Adding a new feature
|
|
248
|
+
|
|
249
|
+
Add a new entry to the `features` array:
|
|
250
|
+
|
|
251
|
+
```ts
|
|
252
|
+
{
|
|
253
|
+
id: 'analytics',
|
|
254
|
+
label: 'Analytics',
|
|
255
|
+
enabled: false, // start disabled; enable per-org when ready
|
|
256
|
+
entityIds: [],
|
|
257
|
+
surfaceIds: [],
|
|
258
|
+
resourceIds: [],
|
|
259
|
+
capabilityIds: []
|
|
260
|
+
}
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
Then proceed with the full manifest, route, and gating steps in [add-a-feature.md](add-a-feature.md).
|
|
264
|
+
The org model change above is Step 2 of that recipe.
|
|
265
|
+
|
|
266
|
+
---
|
|
267
|
+
|
|
268
|
+
### Disabling a default feature
|
|
269
|
+
|
|
270
|
+
Set `enabled: false` on the feature entry. The nav item disappears and `FeatureGuard` blocks
|
|
271
|
+
direct URL access:
|
|
272
|
+
|
|
273
|
+
```ts
|
|
274
|
+
{
|
|
275
|
+
id: 'seo',
|
|
276
|
+
label: 'SEO',
|
|
277
|
+
enabled: false, // nav item hidden, routes redirect to home
|
|
278
|
+
entityIds: [],
|
|
279
|
+
surfaceIds: [],
|
|
280
|
+
resourceIds: [],
|
|
281
|
+
capabilityIds: []
|
|
282
|
+
}
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
The `seo` feature ships disabled by default. If your project does not use `monitoring` either,
|
|
286
|
+
set it to `enabled: false` as well.
|
|
287
|
+
|
|
288
|
+
---
|
|
289
|
+
|
|
290
|
+
### Adding entities to a feature
|
|
291
|
+
|
|
292
|
+
Entity IDs are dot-namespaced strings (`domain.type`). Add to both the feature and any surfaces
|
|
293
|
+
that display those entities:
|
|
294
|
+
|
|
295
|
+
```ts
|
|
296
|
+
// In features array:
|
|
297
|
+
{
|
|
298
|
+
id: 'crm',
|
|
299
|
+
entityIds: ['crm.deal', 'crm.contact'], // added crm.contact
|
|
300
|
+
// ...
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// In the surface that shows contacts:
|
|
304
|
+
{
|
|
305
|
+
id: 'crm.pipeline',
|
|
306
|
+
entityIds: ['crm.deal', 'crm.contact'], // mirror the addition
|
|
307
|
+
// ...
|
|
308
|
+
}
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
Entity IDs inform the organization graph node taxonomy. They do not require registration anywhere
|
|
312
|
+
else in the platform; just keep them consistent across feature and surface declarations.
|
|
313
|
+
|
|
314
|
+
---
|
|
315
|
+
|
|
316
|
+
### Customizing feature labels and descriptions
|
|
317
|
+
|
|
318
|
+
Edit `label` and `description` directly on the feature entry. These values propagate to nav
|
|
319
|
+
label resolution and the settings UI:
|
|
320
|
+
|
|
321
|
+
```ts
|
|
322
|
+
{
|
|
323
|
+
id: 'lead-gen',
|
|
324
|
+
label: 'Prospecting', // override the default 'Lead Gen' label
|
|
325
|
+
description: 'Find and qualify new opportunities',
|
|
326
|
+
// ...
|
|
327
|
+
}
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
If the `FeatureModule` manifest also declares a `navEntry.label`, the manifest label wins in the
|
|
331
|
+
nav sidebar. The feature `label` is the fallback when no manifest override exists.
|
|
332
|
+
|
|
333
|
+
---
|
|
334
|
+
|
|
335
|
+
### Adding surfaces
|
|
336
|
+
|
|
337
|
+
Declare the surface under `navigation.surfaces` and cross-reference it from the owning feature.
|
|
338
|
+
Both sides must be consistent (schema validates bidirectionality):
|
|
339
|
+
|
|
340
|
+
```ts
|
|
341
|
+
// In navigation.surfaces:
|
|
342
|
+
{
|
|
343
|
+
id: 'analytics.dashboard',
|
|
344
|
+
label: 'Analytics',
|
|
345
|
+
path: '/analytics',
|
|
346
|
+
surfaceType: 'dashboard',
|
|
347
|
+
featureId: 'analytics',
|
|
348
|
+
featureIds: ['analytics'],
|
|
349
|
+
entityIds: [],
|
|
350
|
+
resourceIds: [],
|
|
351
|
+
capabilityIds: ['analytics.view']
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// In the analytics feature entry:
|
|
355
|
+
{
|
|
356
|
+
id: 'analytics',
|
|
357
|
+
surfaceIds: ['analytics.dashboard'], // must reference the surface ID above
|
|
358
|
+
capabilityIds: ['analytics.view'],
|
|
359
|
+
// ...
|
|
360
|
+
}
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
---
|
|
364
|
+
|
|
365
|
+
## How It Connects
|
|
366
|
+
|
|
367
|
+
The organization model flows through the runtime in one direction:
|
|
368
|
+
|
|
369
|
+
1. `foundations/config/organization-model.ts` calls `resolveOrganizationModel` at module load.
|
|
370
|
+
The resolved `canonicalOrganizationModel` is exported.
|
|
371
|
+
2. `ui/src/routes/__root.tsx` imports `canonicalOrganizationModel` and passes it to
|
|
372
|
+
`ElevasisFeaturesProvider` along with the `FEATURE_MANIFESTS` array.
|
|
373
|
+
3. `ElevasisFeaturesProvider` validates each manifest's `featureId` against the model's `features`
|
|
374
|
+
array. Unknown `featureId` values throw at startup.
|
|
375
|
+
4. At runtime, the provider uses `features[n].enabled` to decide which nav entries to show.
|
|
376
|
+
`FeatureGuard` reads the same value to allow or block route access.
|
|
377
|
+
5. The organization graph reads `features`, `navigation.surfaces`, `entityIds`, and `resourceMappings`
|
|
378
|
+
to build the operational topology visualization.
|
|
379
|
+
|
|
380
|
+
The contract flows one way: model defines, runtime reads. No part of the UI runtime writes back
|
|
381
|
+
to the model.
|
|
382
|
+
|
|
383
|
+
---
|
|
384
|
+
|
|
385
|
+
## Resolve Behavior
|
|
386
|
+
|
|
387
|
+
`resolveOrganizationModel(override)` does the following:
|
|
388
|
+
|
|
389
|
+
1. Deep-merges the override onto `DEFAULT_ORGANIZATION_MODEL`. Plain objects are merged key by key.
|
|
390
|
+
**Arrays are replaced wholesale** -- if you supply `features: [...]`, your array replaces the
|
|
391
|
+
defaults entirely, not appends to them.
|
|
392
|
+
2. If you override `navigation.surfaces` without overriding `navigation.groups`, the resolver
|
|
393
|
+
automatically filters out any groups whose `surfaceIds` reference surfaces that no longer exist
|
|
394
|
+
in your override. This prevents dangling group references from causing a parse failure.
|
|
395
|
+
3. Parses the merged result through `OrganizationModelSchema`. Any bidirectional reference
|
|
396
|
+
inconsistency (feature \<-\> surface, surface \<-\> resource mapping) throws a Zod validation
|
|
397
|
+
error with a specific path and message pointing to the offending reference.
|
|
398
|
+
|
|
399
|
+
When you see a startup error from `resolveOrganizationModel`, read the Zod issue path carefully.
|
|
400
|
+
It will identify which specific field and array index contains the broken reference.
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Extend a Base Entity
|
|
3
|
+
description: Add project-specific metadata to the canonical entity shapes (Project, Deal, Company, etc.) from @elevasis/core/entities using the TMeta extension slot.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Extend a Base Entity
|
|
7
|
+
|
|
8
|
+
Workflows and UI features often operate on domain entities such as projects, deals, companies, or contacts. Rather than each project declaring its own shape from scratch, `@elevasis/core/entities` provides typed base interfaces generic over a `<TMeta>` slot. External projects extend these to add project-specific fields while keeping the canonical shape stable and interoperable with platform tooling.
|
|
9
|
+
|
|
10
|
+
The canonical demo lives in `foundations/types/entities.ts` of any scaffold project.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Available Base Entities
|
|
15
|
+
|
|
16
|
+
Each entity ships as a TypeScript interface, a Zod schema, and an Input type:
|
|
17
|
+
|
|
18
|
+
| Set | Description |
|
|
19
|
+
| -------------------------------------------------------------- | ------------------------------------------- |
|
|
20
|
+
| `BaseProject` / `BaseProjectSchema` / `BaseProjectInput` | Client or internal project record |
|
|
21
|
+
| `BaseMilestone` / `BaseMilestoneSchema` / `BaseMilestoneInput` | Milestone within a project |
|
|
22
|
+
| `BaseTask` / `BaseTaskSchema` / `BaseTaskInput` | Discrete task within a project or milestone |
|
|
23
|
+
| `BaseDeal` / `BaseDealSchema` / `BaseDealInput` | Sales or partnership deal record |
|
|
24
|
+
| `BaseCompany` / `BaseCompanySchema` / `BaseCompanyInput` | Company / account record |
|
|
25
|
+
| `BaseContact` / `BaseContactSchema` / `BaseContactInput` | Individual contact record |
|
|
26
|
+
|
|
27
|
+
All imports come from `@elevasis/core/entities`.
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Recipe 1 -- Extend a base entity with custom metadata
|
|
32
|
+
|
|
33
|
+
Place this in `foundations/types/entities.ts`. This is the primary pattern -- the scaffold template ships this exact example.
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
import { z } from 'zod'
|
|
37
|
+
import type { BaseProject, BaseDeal } from '@elevasis/core/entities'
|
|
38
|
+
import { BaseProjectSchema, BaseDealSchema } from '@elevasis/core/entities'
|
|
39
|
+
|
|
40
|
+
// -- Project: extending metadata on a base entity --
|
|
41
|
+
|
|
42
|
+
export const ProjectMetaSchema = z.object({
|
|
43
|
+
budget: z.number().nonnegative(),
|
|
44
|
+
clientPriority: z.enum(['low', 'medium', 'high'])
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
export type ProjectMeta = z.infer<typeof ProjectMetaSchema>
|
|
48
|
+
|
|
49
|
+
export const ProjectSchema = BaseProjectSchema.extend({ metadata: ProjectMetaSchema })
|
|
50
|
+
|
|
51
|
+
export type Project = BaseProject<ProjectMeta>
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Key points:
|
|
55
|
+
|
|
56
|
+
- `BaseProjectSchema.extend({ metadata: ... })` merges your metadata Zod schema into the validated shape. Zod handles the rest.
|
|
57
|
+
- `BaseProject<ProjectMeta>` infers the TypeScript type with your metadata typed correctly.
|
|
58
|
+
- Only the `metadata` field is extended -- all other base fields (`id`, `organizationId`, `name`, `status`, `createdAt`, `updatedAt`, etc.) are inherited unchanged.
|
|
59
|
+
- This file is the single source of truth for entity shapes across `foundations/`, `operations/`, and `ui/`.
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## Recipe 2 -- Use a base entity as-is
|
|
64
|
+
|
|
65
|
+
When the base shape covers everything the project needs, skip the extension entirely:
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
import type { BaseDeal } from '@elevasis/core/entities'
|
|
69
|
+
import { BaseDealSchema } from '@elevasis/core/entities'
|
|
70
|
+
|
|
71
|
+
export const DealSchema = BaseDealSchema
|
|
72
|
+
|
|
73
|
+
export type Deal = BaseDeal
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
`BaseDeal` without a type argument defaults `TMeta` to an empty object (`{}`), which Zod validates as `z.object({})`. Use this path when the project has no per-deal custom fields -- you still get the canonical shape and full Zod validation for free.
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## Recipe 3 -- Reference entity types from a workflow input schema
|
|
81
|
+
|
|
82
|
+
Workflows that operate on projects or deals should reference the project-local entity types rather than redeclaring the shape. Add this to `operations/src/<workflow>/workflow.ts`:
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
import { z } from 'zod'
|
|
86
|
+
import type { WorkflowDefinition } from '@elevasis/sdk'
|
|
87
|
+
import { ProjectSchema } from '@foundation/types/entities'
|
|
88
|
+
|
|
89
|
+
export const myWorkflow: WorkflowDefinition = {
|
|
90
|
+
id: 'my-workflow',
|
|
91
|
+
inputSchema: z.object({
|
|
92
|
+
project: ProjectSchema,
|
|
93
|
+
notes: z.string().optional()
|
|
94
|
+
}),
|
|
95
|
+
// ...
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
The `@foundation/*` alias maps to the `foundations/` workspace package. This keeps entity contracts in one place and ensures the workflow input type matches whatever the UI passes as the execution payload.
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## Recipe 4 -- Reference entity types from the UI
|
|
104
|
+
|
|
105
|
+
UI components accept entity types directly in props. The entity type flows from the `foundations` package through the component into the workflow execution payload:
|
|
106
|
+
|
|
107
|
+
```tsx
|
|
108
|
+
import type { Project } from '@foundation/types/entities'
|
|
109
|
+
|
|
110
|
+
interface ProjectCardProps {
|
|
111
|
+
project: Project
|
|
112
|
+
onExecute?: (project: Project) => void
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function ProjectCard({ project, onExecute }: ProjectCardProps) {
|
|
116
|
+
return (
|
|
117
|
+
<button onClick={() => onExecute?.(project)}>
|
|
118
|
+
Run workflow for {project.name}
|
|
119
|
+
</button>
|
|
120
|
+
)
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
When wiring this to a `RunResourceButton`, the entity instance becomes the workflow input. See UI Recipes recipe 6 (`Execute a Resource from a Surface`) at `../ui/recipes.md` for the end-to-end pattern where the entity type flows into a resource execution via `RunResourceButton`.
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## Verification
|
|
129
|
+
|
|
130
|
+
- **Type-check foundations:** `pnpm -C foundations test` runs the Vitest suite. The template ships `foundations/types/entities.test.ts` as a working smoke-check -- it `safeParse`s a valid project (expects success) and an invalid one (expects failure). Run this after any schema change.
|
|
131
|
+
- **Round-trip safeParse:** Call `ProjectSchema.safeParse(candidateObject)` in a test or REPL to confirm the Zod shape accepts the data your workflows and UI will produce.
|
|
132
|
+
- **Cross-package type check:** `pnpm -C ui build` will surface any TypeScript errors if a UI component or hook passes a mismatched entity type to a workflow input.
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## Cross-references
|
|
137
|
+
|
|
138
|
+
- [./add-a-resource.md](./add-a-resource.md) -- resource authoring that consumes entity types in `inputSchema`
|
|
139
|
+
- [../ui/recipes.md](../ui/recipes.md) recipe 6 -- execute a resource from a surface, with entity-typed input via `RunResourceButton`
|
|
140
|
+
- [../reference/contracts.md](../reference/contracts.md) -- auto-generated TypeScript contract shapes for all Organization OS types
|
|
@@ -27,19 +27,23 @@ Do not substitute one for the other. `FeatureGuard` reads feature flags; `AdminG
|
|
|
27
27
|
|
|
28
28
|
## 2. Feature gate -- org level
|
|
29
29
|
|
|
30
|
-
Ensure the
|
|
30
|
+
Ensure a feature object with the matching `id` exists in the org model. File: `foundations/config/organization-model.ts`.
|
|
31
31
|
|
|
32
32
|
```ts
|
|
33
|
-
|
|
34
|
-
features: { enabled: { ..., analytics: false } }
|
|
33
|
+
import { defineOrganizationModel, OPERATIONS_FEATURE_ID, CRM_FEATURE_ID } from '@elevasis/core/organization-model'
|
|
35
34
|
|
|
36
|
-
// In
|
|
37
|
-
|
|
35
|
+
// In the features[] array inside defineOrganizationModel:
|
|
36
|
+
features: [
|
|
37
|
+
// ... existing features
|
|
38
|
+
{ id: 'analytics', label: 'Analytics', enabled: false, entityIds: [], surfaceIds: [], resourceIds: [], capabilityIds: [] }
|
|
39
|
+
]
|
|
38
40
|
```
|
|
39
41
|
|
|
40
|
-
|
|
42
|
+
Use typed constants for the 7 platform features (`CRM_FEATURE_ID`, `OPERATIONS_FEATURE_ID`, etc.); use string literals for project-specific features you invent. In this example, `'analytics'` is a project-local feature and stays as a string literal.
|
|
41
43
|
|
|
42
|
-
|
|
44
|
+
See [add-a-feature.md](add-a-feature.md) step 2 for the full feature object shape.
|
|
45
|
+
|
|
46
|
+
Set `enabled: true` to enable for all members by default.
|
|
43
47
|
|
|
44
48
|
---
|
|
45
49
|
|
|
@@ -67,7 +71,7 @@ function AnalyticsLayout() {
|
|
|
67
71
|
|
|
68
72
|
`FeatureGuard` redirects to `/` with a Mantine notification when the feature is off. All child routes under this layout are automatically protected -- no need to repeat the guard in children.
|
|
69
73
|
|
|
70
|
-
Import path: `@/features/auth/guards/FeatureGuard` (template-local). Do not import `FeatureGuard` from `@elevasis/ui/features` -- that version does not close over the local
|
|
74
|
+
Import path: `@/features/auth/guards/FeatureGuard` (template-local). Do not import `FeatureGuard` from `@elevasis/ui/features` -- that version does not close over the local org model. See [feature-flags-and-gating.md](../ui/feature-flags-and-gating.md) for the import-paths table.
|
|
71
75
|
|
|
72
76
|
---
|
|
73
77
|
|
|
@@ -107,7 +111,7 @@ navEntry: {
|
|
|
107
111
|
label: 'Analytics',
|
|
108
112
|
icon: IconChartBar,
|
|
109
113
|
link: '/analytics',
|
|
110
|
-
featureKey: 'analytics' // optional --
|
|
114
|
+
featureKey: 'analytics' // optional -- featureId already gates the whole module
|
|
111
115
|
}
|
|
112
116
|
|
|
113
117
|
// In nav-items.ts (app-local nav):
|
|
@@ -115,7 +119,7 @@ navEntry: {
|
|
|
115
119
|
{ label: 'Admin', icon: IconShield, link: '/admin', requiresAdmin: true }
|
|
116
120
|
```
|
|
117
121
|
|
|
118
|
-
`featureKey` on a nav entry is a display hint only -- it does not protect the route. Always pair with a route-level `FeatureGuard`. See [glossary.md](../reference/glossary.md) under **accessFeatureKey vs featureKey** for the distinction.
|
|
122
|
+
`featureKey` on a nav entry is a display hint only -- it does not protect the route. Always pair with a route-level `FeatureGuard`. See [glossary.md](../reference/glossary.md) under **accessFeatureKey vs featureKey** for the distinction. When the `featureKey` is one of the 7 platform features, prefer the typed constant (e.g., `featureKey: CRM_FEATURE_ID`) over a string literal to catch renames at compile time.
|
|
119
123
|
|
|
120
124
|
---
|
|
121
125
|
|
|
@@ -126,11 +130,13 @@ navEntry: {
|
|
|
126
130
|
```ts
|
|
127
131
|
// Disable analytics for a specific member (stored in their membership record):
|
|
128
132
|
{
|
|
129
|
-
features: {
|
|
133
|
+
features: { analytics: false }
|
|
130
134
|
}
|
|
131
135
|
```
|
|
132
136
|
|
|
133
|
-
|
|
137
|
+
`MembershipFeatureConfig.features` is `Record<string, boolean>` -- a dynamic map keyed by feature ID. Any feature ID from the org model can be overridden per member without schema changes.
|
|
138
|
+
|
|
139
|
+
**Settings asymmetry -- read this before writing membership config.** The `settings` feature is intentionally excluded from per-member overrides: settings access is controlled by `requiresAdmin` and `AdminGuard`, not per-member feature flags. Writing a `settings` key to `org_memberships.config` has no effect. See [contracts.md](../reference/contracts.md#membershipfeatureconfig) for the full callout, and [glossary.md](../reference/glossary.md) under **Settings asymmetry**.
|
|
134
140
|
|
|
135
141
|
Membership config is read/written directly via the Supabase client (`org_memberships.config` column). There is no dedicated API route for bulk updates.
|
|
136
142
|
|
|
@@ -7,14 +7,14 @@ description: Terse end-to-end walkthroughs for the three most common authoring t
|
|
|
7
7
|
|
|
8
8
|
Three sequential walkthroughs for common authoring tasks. Each recipe links into the reference docs rather than duplicating them.
|
|
9
9
|
|
|
10
|
-
Before starting, read [glossary.md](../reference/glossary.md) to disambiguate overloaded terms (Feature, Resource,
|
|
10
|
+
Before starting, read [glossary.md](../reference/glossary.md) to disambiguate overloaded terms (Feature, Resource, featureId, Settings asymmetry, Topology).
|
|
11
11
|
|
|
12
12
|
---
|
|
13
13
|
|
|
14
14
|
## Recipes
|
|
15
15
|
|
|
16
16
|
**[Add a Feature](add-a-feature.md)**
|
|
17
|
-
You want a new shell feature (e.g., `analytics`) with its own nav entry, sidebar, routes, and org-model gate. Covers org
|
|
17
|
+
You want a new shell feature (e.g., `analytics`) with its own nav entry, sidebar, routes, and org-model gate. Covers adding a feature object to the org model, `FeatureModule` manifest authoring, route creation, and gating.
|
|
18
18
|
|
|
19
19
|
**[Add a Resource](add-a-resource.md)**
|
|
20
20
|
You want a new workflow or agent deployed to the platform. Covers `WorkflowDefinition` authoring, `DeploymentSpec` registration, relationship declarations, domain mapping, and CLI verification.
|
|
@@ -26,7 +26,7 @@ You want to restrict a route, nav item, or UI element by feature flag or admin r
|
|
|
26
26
|
|
|
27
27
|
## Reference docs these recipes link into
|
|
28
28
|
|
|
29
|
-
- [glossary.md](../reference/glossary.md) -- term disambiguation for Feature, Resource,
|
|
29
|
+
- [glossary.md](../reference/glossary.md) -- term disambiguation for Feature, Resource, featureId, Topology, Settings asymmetry
|
|
30
30
|
- [contracts.md](../reference/contracts.md) -- TypeScript shapes: `FeatureModule`, `FeatureNavEntry`, `OrganizationModel`, `MembershipFeatureConfig`
|
|
31
31
|
- [feature-flags-and-gating.md](../ui/feature-flags-and-gating.md) -- full three-concept gating model
|
|
32
32
|
- [workflow-recipes.md](../operations/workflow-recipes.md) -- workflow anatomy, adapters, trigger patterns
|