@elevasis/sdk 1.5.3 → 1.5.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/dist/cli.cjs +967 -57
  2. package/dist/index.d.ts +94 -110
  3. package/package.json +2 -2
  4. package/reference/_navigation.md +11 -1
  5. package/reference/_reference-manifest.json +70 -0
  6. package/reference/claude-config/commands/submit-issue.md +11 -0
  7. package/reference/claude-config/hooks/post-edit-validate.mjs +109 -0
  8. package/reference/claude-config/hooks/tool-failure-recovery.mjs +73 -0
  9. package/reference/claude-config/rules/deployment.md +57 -0
  10. package/reference/claude-config/rules/docs.md +26 -0
  11. package/reference/claude-config/rules/error-handling.md +56 -0
  12. package/reference/claude-config/rules/execution.md +40 -0
  13. package/reference/claude-config/rules/frontend.md +43 -0
  14. package/reference/claude-config/rules/observability.md +31 -0
  15. package/reference/claude-config/rules/organization-os.md +62 -0
  16. package/reference/claude-config/rules/platform.md +41 -0
  17. package/reference/claude-config/rules/shared-types.md +46 -0
  18. package/reference/claude-config/rules/task-tracking.md +47 -0
  19. package/reference/claude-config/scripts/statusline-command.js +18 -0
  20. package/reference/claude-config/settings.json +30 -0
  21. package/reference/claude-config/skills/deploy/SKILL.md +166 -0
  22. package/reference/claude-config/skills/dsp/SKILL.md +66 -0
  23. package/reference/claude-config/skills/elevasis/SKILL.md +239 -0
  24. package/reference/claude-config/skills/explore/SKILL.md +78 -0
  25. package/reference/claude-config/skills/project/SKILL.md +918 -0
  26. package/reference/claude-config/skills/save/SKILL.md +197 -0
  27. package/reference/claude-config/skills/setup/SKILL.md +210 -0
  28. package/reference/claude-config/skills/status/SKILL.md +60 -0
  29. package/reference/claude-config/skills/submit-issue/SKILL.md +165 -0
  30. package/reference/claude-config/skills/sync/SKILL.md +81 -0
  31. package/reference/cli.mdx +19 -4
  32. package/reference/deployment/provided-features.mdx +24 -2
  33. package/reference/framework/agent.mdx +12 -4
  34. package/reference/framework/project-structure.mdx +9 -3
  35. package/reference/packages/core/src/README.md +1 -1
  36. package/reference/packages/core/src/business/README.md +52 -0
  37. package/reference/packages/core/src/organization-model/README.md +25 -26
  38. package/reference/packages/ui/src/app/README.md +24 -0
  39. package/reference/platform-tools/type-safety.mdx +0 -10
  40. package/reference/scaffold/core/organization-graph.mdx +37 -28
  41. package/reference/scaffold/core/organization-model.mdx +34 -36
  42. package/reference/scaffold/index.mdx +1 -0
  43. package/reference/scaffold/operations/propagation-pipeline.md +7 -3
  44. package/reference/scaffold/operations/scaffold-maintenance.md +2 -2
  45. package/reference/scaffold/operations/workflow-recipes.md +18 -1
  46. package/reference/scaffold/recipes/add-a-feature.md +37 -21
  47. package/reference/scaffold/recipes/add-a-resource.md +4 -2
  48. package/reference/scaffold/recipes/customize-organization-model.md +400 -0
  49. package/reference/scaffold/recipes/extend-a-base-entity.md +140 -0
  50. package/reference/scaffold/recipes/gate-by-feature-or-admin.md +18 -12
  51. package/reference/scaffold/recipes/index.md +3 -3
  52. package/reference/scaffold/reference/contracts.md +11 -32
  53. package/reference/scaffold/reference/feature-registry.md +10 -9
  54. package/reference/scaffold/reference/glossary.md +14 -18
  55. package/reference/scaffold/ui/customization.md +2 -2
  56. package/reference/scaffold/ui/feature-flags-and-gating.md +40 -54
  57. package/reference/scaffold/ui/feature-shell.mdx +22 -23
  58. package/reference/scaffold/ui/recipes.md +118 -3
@@ -1,3 +1,4 @@
1
+ <!-- Auto-generated on 2026-04-19T10:47:49.477Z by scripts/monorepo/generate-scaffold-contracts.js -->
1
2
  ---
2
3
  title: Reference Contracts
3
4
  description: Auto-generated TypeScript contracts for SDK consumers. Do not edit manually.
@@ -37,16 +38,10 @@ export type OrganizationModelLeadGen = z.infer<typeof OrganizationModelLeadGenSc
37
38
  export type OrganizationModelDelivery = z.infer<typeof OrganizationModelDeliverySchema>
38
39
  ```
39
40
 
40
- ### `OrganizationModelFeatures`
41
+ ### `OrganizationModelFeature`
41
42
 
42
43
  ```typescript
43
- export type OrganizationModelFeatures = z.infer<typeof OrganizationModelFeaturesSchema>
44
- ```
45
-
46
- ### `OrganizationModelFeatureKey`
47
-
48
- ```typescript
49
- export type OrganizationModelFeatureKey = z.infer<typeof FeatureKeySchema>
44
+ export type OrganizationModelFeature = z.infer<typeof FeatureSchema>
50
45
  ```
51
46
 
52
47
  ### `OrganizationModelNavigation`
@@ -61,12 +56,6 @@ export type OrganizationModelNavigation = z.infer<typeof OrganizationModelNaviga
61
56
  export type OrganizationModelSurface = z.infer<typeof SurfaceDefinitionSchema>
62
57
  ```
63
58
 
64
- ### `OrganizationModelSemanticDomain`
65
-
66
- ```typescript
67
- export type OrganizationModelSemanticDomain = z.infer<typeof SemanticDomainSchema>
68
- ```
69
-
70
59
  ### `OrganizationModelResourceMapping`
71
60
 
72
61
  ```typescript
@@ -76,11 +65,7 @@ export type OrganizationModelResourceMapping = z.infer<typeof ResourceMappingSch
76
65
  ### `DeepPartial`
77
66
 
78
67
  ```typescript
79
- export type DeepPartial<T> = T extends Array<infer U>
80
- ? Array<DeepPartial<U>>
81
- : T extends object
82
- ? { [K in keyof T]?: DeepPartial<T[K]> }
83
- : T
68
+ export type DeepPartial<T> = T extends Array<infer U> ? Array<DeepPartial<U>> : T extends object ? { [K in keyof T]?: DeepPartial<T[K]> } : T
84
69
  ```
85
70
 
86
71
  ## Feature System
@@ -121,15 +106,10 @@ export type FeatureSidebarComponent = ComponentType
121
106
 
122
107
  ```typescript
123
108
  export interface FeatureModule {
124
- /** Unique stable identifier for this feature (e.g. `'crm'`, `'delivery'`). */
109
+ /** Unique stable identifier for this feature (e.g. `'crm'`, `'projects'`). */
125
110
  key: string
126
- /** Feature key used for access-flag gating in the organization model. */
127
- accessFeatureKey: OrganizationModelFeatureKey
128
- /**
129
- * Semantic domain identifiers contributed by this feature.
130
- * Merged with surface-derived domain IDs during resolution.
131
- */
132
- domainIds?: OrganizationModelSemanticDomain['id'][]
111
+ /** Feature ID used for access-flag gating — must match the `id` of a feature in the organization model. */
112
+ featureId: string
133
113
  /**
134
114
  * Capability identifiers contributed by this feature.
135
115
  * Merged into `ResolvedFeatureSemantics.capabilityIds` at resolution time.
@@ -156,7 +136,7 @@ export interface FeatureModule {
156
136
 
157
137
  ```typescript
158
138
  export interface ResolvedFeatureAccess {
159
- featureKey: string
139
+ featureId: string
160
140
  enabled: boolean
161
141
  }
162
142
  ```
@@ -165,7 +145,6 @@ export interface ResolvedFeatureAccess {
165
145
 
166
146
  ```typescript
167
147
  export interface ResolvedFeatureSemantics {
168
- domainIds: OrganizationModelSemanticDomain['id'][]
169
148
  capabilityIds: string[]
170
149
  surfaceIds: string[]
171
150
  surfaces: OrganizationModelSurface[]
@@ -199,7 +178,7 @@ export type ShellNavSource = 'app' | 'feature'
199
178
  export interface ResolvedShellNavItem extends FeatureNavEntry {
200
179
  placement: ShellNavPlacement
201
180
  source: ShellNavSource
202
- accessFeatureKey?: string
181
+ featureId?: string
203
182
  }
204
183
  ```
205
184
 
@@ -262,7 +241,7 @@ export interface OrganizationGraphContextValue {
262
241
  surfaceId?: string
263
242
  surfacePath?: string
264
243
  surfaceType?: OrganizationModelSurface['surfaceType']
265
- featureKey?: OrganizationModelFeatureKey
244
+ featureId?: string
266
245
  }
267
246
  ```
268
247
 
@@ -500,7 +479,7 @@ export interface ResourceList {
500
479
 
501
480
  ```typescript
502
481
  /** Webhook provider identifiers */
503
- export type WebhookProviderType = 'cal-com' | 'stripe' | 'signature-api' | 'instantly' | 'apify'
482
+ export type WebhookProviderType = 'cal-com' | 'stripe' | 'signature-api' | 'instantly' | 'apify' | 'test'
504
483
 
505
484
  /** Webhook trigger configuration */
506
485
  ```
@@ -7,24 +7,25 @@ description: Auto-generated catalog of registered feature manifests. Do not edit
7
7
 
8
8
  ## Registered Feature Manifests
9
9
 
10
- | Feature Key | Access Key | Nav Label | Domains | Routes |
10
+ | Feature Key | Feature ID | Nav Label | Domains | Routes |
11
11
  | --- | --- | --- | --- | --- |
12
- | `crm` | `acquisition` | CRM | crm | /crm |
13
- | `delivery` | `delivery` | Projects | delivery | /projects |
14
- | `lead-gen` | `acquisition` | Lead Gen | lead-gen | /lead-gen |
12
+ | `crm` | `crm` | CRM | | /crm |
13
+ | `delivery` | `—` | Projects | | /projects |
14
+ | `lead-gen` | `lead-gen` | Lead Gen | | /lead-gen |
15
15
  | `monitoring` | `monitoring` | Monitoring | — | — |
16
- | `operations` | `operations` | Operations | operations | /operations |
16
+ | `operations` | `operations` | Operations | | /operations |
17
17
  | `seo` | `seo` | — | — | /seo |
18
18
  | `settings` | `settings` | Settings | — | — |
19
19
 
20
20
  > **Note:** `auth` and `dashboard` are not in the registry — they are app-shell concerns, not gated features.
21
21
 
22
- ## OrganizationModelFeatureKey Values
22
+ ## Default Feature IDs
23
23
 
24
- Values defined in `FeatureKeySchema` (`packages/core/src/organization-model/domains/features.ts`):
24
+ Feature IDs defined in `DEFAULT_ORGANIZATION_MODEL.features` (`packages/core/src/organization-model/defaults.ts`):
25
25
 
26
26
  ```typescript
27
- type OrganizationModelFeatureKey = 'acquisition' | 'delivery' | 'operations' | 'monitoring' | 'settings' | 'seo' | 'calibration'
27
+ // FeatureSchema: { id, label, enabled, color?, icon?, entityIds, surfaceIds, resourceIds, capabilityIds }
28
+ type DefaultFeatureId =
28
29
  ```
29
30
 
30
- These are the valid values for `accessFeatureKey` in a `FeatureModule` manifest and for feature-flag gating in the organization model.
31
+ 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.
@@ -13,7 +13,7 @@ This condensed version covers every ambiguity-prone term a template consumer or
13
13
 
14
14
  ## Terms
15
15
 
16
- **accessFeatureKey** -- the `OrganizationModelFeatureKey` a `FeatureModule` declares so the provider knows which org-level gate to check. Required field on every `FeatureModule`. Distinct from the nav-item `featureKey` (see below). Shell keys like `crm` and `lead-gen` map to grouped org-model keys (`acquisition`) via `FEATURE_KEY_ALIASES` -- this aliasing is automatic; callers do not need to translate manually.
16
+ **featureId** (on FeatureModule) -- the `id` string a `FeatureModule` declares so the provider knows which entry in `OrganizationModel.features` to check for access gating. Required field on every `FeatureModule`. Must match the `id` of an entry in the features array (e.g. `'crm'`, `'projects'`). Distinct from the nav-item `featureKey` (see below). No aliasing layer -- IDs are direct matches.
17
17
 
18
18
  **AdminGuard** -- route-level admin wrapper from `@elevasis/ui/features/auth`. Wraps routes restricted to admin members. Must nest inside `ProtectedRoute`. Does not replace `requiresAdmin` on nav entries -- use both when both route access and nav visibility need admin enforcement.
19
19
 
@@ -21,45 +21,41 @@ This condensed version covers every ambiguity-prone term a template consumer or
21
21
 
22
22
  **DeploymentSpec** -- the complete resource collection for one organization (`workflows`, `agents`, `triggers`, `integrations`, `relationships`, `externalResources`, `humanCheckpoints`). Defined in `@repo/core`. The `operations/src/index.ts` file in each external project exports one `DeploymentSpec`.
23
23
 
24
- **Domain** -- a semantic business area in `OrganizationModel.domains`. Four defaults: `crm`, `lead-gen`, `delivery`, `operations`. Domains group entity IDs, surface IDs, resource IDs, and capability IDs into a coherent business area. Semantic, not navigational -- describes what business area a thing belongs to, not which access key gates it. Distinct from "feature": a domain describes business meaning; a feature key describes access and enablement.
24
+ **Domain** -- removed concept. What was previously a `SemanticDomain` entry in `OrganizationModel.domains` is now expressed directly as a **Feature** in the `features` array. The `domains` field no longer exists on `OrganizationModel`. Semantic grouping (entity IDs, surface IDs, resource IDs, capability IDs) now lives on each `OrganizationModelFeature` object.
25
25
 
26
- **FEATURE_KEY_ALIASES** -- the alias map in `@elevasis/ui` that bridges shell feature-module keys to org-model grouped keys: `crm -> acquisition`, `lead-gen -> acquisition`, `projects -> delivery`. Used internally by `createFeatureAccessHook` so `isFeatureEnabled('crm')` resolves correctly against `MembershipFeatureConfig.features.acquisition`.
27
-
28
- **FoundationLegacyFeatureKey** -- template-local union for feature keys defined in `foundations/config/organization-model.ts`. Distinct from the published `OrganizationModelFeatureKey` (seven-key union from `@elevasis/core`). Extension point for template-specific feature identity without modifying the published core union. `FoundationFeatureKey` combines both (`OrganizationModelFeatureKey | FoundationLegacyFeatureKey`). `FEATURE_KEY_ALIASES` maps between shell keys and org-model grouped keys at runtime.
29
-
30
- **Feature** -- an overloaded term. Always qualify which layer is in scope:
26
+ **Feature** -- the unified concept replacing the former three-layer system (feature keys, semantic domains, feature modules). Always qualify which layer is in scope:
31
27
 
32
28
  - **Platform capability** -- a product area (Execution Engine, Workflows, Agents, etc.). Not one-to-one with shell features.
33
- - **Shell FeatureModule** -- a manifest-backed UI feature registered with `ElevasisFeaturesProvider`. Seven manifest-backed: `lead-gen`, `crm`, `delivery`, `operations`, `monitoring`, `settings`, `seo`. Two utility (no manifest): `auth`, `dashboard`.
34
- - **Organization-model feature key** (`OrganizationModelFeatureKey`) -- grouped access key. Seven values: `acquisition`, `delivery`, `operations`, `monitoring`, `settings`, `seo`, `calibration`. Controls org-level gating and membership overrides.
29
+ - **Organization-model feature** (`OrganizationModelFeature`) -- a single entry in `OrganizationModel.features`. Combines access gating (`enabled`), semantic grouping (`entityIds`, `surfaceIds`, `resourceIds`, `capabilityIds`), and display metadata (`label`, `description`, `color`, `icon`). Seven defaults: `crm`, `lead-gen`, `projects`, `operations`, `monitoring`, `settings`, `seo`.
30
+ - **Shell FeatureModule** -- a manifest-backed UI feature registered with `ElevasisFeaturesProvider`. Declares `featureId` matching an org-model feature ID. Seven manifest-backed: `lead-gen`, `crm`, `projects`, `operations`, `monitoring`, `settings`, `seo`. Two utility (no manifest): `auth`, `dashboard`.
35
31
 
36
- **featureKey** (nav-item) -- optional field on `FeatureNavEntry` and `FeatureNavLink` that gates a specific nav item's visibility independently of the module's `accessFeatureKey`. Resolved through `FEATURE_KEY_ALIASES`. May be more specific than the module's access key when a nested link needs a narrower gate.
32
+ **featureKey** (nav-item) -- optional field on `FeatureNavEntry` and `FeatureNavLink` that gates a specific nav item's visibility independently of the module's `featureId`. Must match a feature ID in the org model. May be more specific than the module's `featureId` when a nested link needs a narrower gate.
37
33
 
38
34
  **FeatureGuard** -- route-level feature gate from `@elevasis/ui/features/auth`. Blocks access to a route when the resolved org model has the relevant feature key disabled. Must nest inside `ProtectedRoute`. Distinct from `AdminGuard` (admin membership) and `ProtectedRoute` (authentication).
39
35
 
40
- **FeatureModule** -- the manifest contract each shell feature provides to `ElevasisFeaturesProvider`. Defined in `@repo/ui`, NOT `@repo/core`. Fields: `key`, `accessFeatureKey` (required), `domainIds`, `capabilityIds`, `navEntry`, `sidebar`, `subshellRoutes`, `organizationGraph` (Operations-only). Template consumers build a local manifest array and pass it to `ElevasisFeaturesProvider` in `__root.tsx`.
36
+ **FeatureModule** -- the manifest contract each shell feature provides to `ElevasisFeaturesProvider`. Defined in `@repo/ui`, NOT `@repo/core`. Fields: `key`, `featureId` (required -- replaces the former `accessFeatureKey`), `capabilityIds`, `navEntry`, `sidebar`, `subshellRoutes`, `organizationGraph` (Operations-only). Template consumers build a local manifest array and pass it to `ElevasisFeaturesProvider` in `__root.tsx`.
41
37
 
42
38
  **Foundations** -- the adapter layer in `foundations/` (two modules: `config/organization-model.ts` and `types/index.ts`). Source package with no build step. Depends only on `@elevasis/core` (npm) and `zod`. Never import `@repo/core` from foundations -- that would break standalone deployment.
43
39
 
44
40
  **Manifest** -- a `FeatureModule` instance that declares what one shell feature contributes at runtime (nav, sidebar, subshell routes, access key, semantic references). The provider registers an array of manifests at startup and validates them against the resolved org model.
45
41
 
46
- **MembershipFeatureConfig** -- per-member-per-org feature overrides stored in `org_memberships.config`. Six keys: `operations`, `monitoring`, `acquisition`, `delivery`, `calibration`, `seo`. Note: `settings` is absent -- see **Settings asymmetry** below.
42
+ **MembershipFeatureConfig** -- per-member-per-org feature overrides stored in `org_memberships.config`. The `features` field is now `Record<string, boolean>` -- any feature ID can be overridden per member. Previously hardcoded to five keys (`operations`, `monitoring`, `acquisition`, `delivery`, `seo`); now open to any feature ID in the org model.
47
43
 
48
44
  **OrganizationModel** -- the top-level semantic contract for an organization. Published from `@elevasis/core/organization-model`. In the template, authored in `foundations/config/organization-model.ts` and exported as `canonicalOrganizationModel` (passed to `ElevasisFeaturesProvider`) and `organizationModel` (enriched shape for app-local use).
49
45
 
50
- **OrganizationModelFeatureKey** -- seven values: `acquisition`, `delivery`, `operations`, `monitoring`, `settings`, `seo`, `calibration`. These appear in `OrganizationModel.features.enabled` and as `accessFeatureKey` on `FeatureModule` instances. `MembershipFeatureConfig` overrides six of them per member (no `settings` slot -- see **Settings asymmetry**).
46
+ **OrganizationModelFeature** -- the TypeScript type (`z.infer<typeof FeatureSchema>`) for a single entry in `OrganizationModel.features`. Replaces the former `OrganizationModelFeatureKey` (closed enum) and `OrganizationModelSemanticDomain`. Each feature has `id`, `label`, `enabled`, and semantic grouping arrays (`entityIds`, `surfaceIds`, `resourceIds`, `capabilityIds`). Exported from `@elevasis/core/organization-model`.
51
47
 
52
48
  **Provider / ElevasisFeaturesProvider** -- the runtime that registers manifests, resolves feature access against the org model, dispatches subshell routing via `FeatureShell`, and exposes resolved state through `useElevasisFeatures()`. Mounted in `__root.tsx`. Accepts `features`, optional `organizationModel`, and optional `appShellOverrides`.
53
49
 
54
- **Resolved types (ResolvedFeatureModule, ResolvedShellNavItem)** -- provider output. `ResolvedFeatureModule` extends `FeatureModule` with `access` (enabled state) and `semantics` (merged domain, capability, and surface IDs). `ResolvedShellNavItem` extends `FeatureNavEntry` with `placement`, `source`, and `accessFeatureKey`. Both from `@elevasis/ui`.
50
+ **Resolved types (ResolvedFeatureModule, ResolvedShellNavItem)** -- provider output. `ResolvedFeatureModule` extends `FeatureModule` with `access` (enabled state and `featureId`) and `semantics` (merged capability and surface IDs). `ResolvedShellNavItem` extends `FeatureNavEntry` with `placement`, `source`, and `featureId`. Both from `@elevasis/ui`.
55
51
 
56
52
  **Resource** -- an entry in `OrganizationModel.resourceMappings` linking a deployable automation resource into the semantic model. At the registry layer, resources are `WorkflowDefinition` or `AgentDefinition` instances in a `DeploymentSpec`.
57
53
 
58
- **Settings asymmetry** -- `settings` is a valid `OrganizationModelFeatureKey` (org-level, present in `OrganizationModel.features.enabled`). It is absent from `MembershipFeatureConfig` (six keys, no `settings`). The org can disable settings entirely, but per-member disabling is not supported. Settings visibility for individual members is controlled via `requiresAdmin` on the nav entry and `AdminGuard` on routes.
54
+ **Settings asymmetry** -- `settings` is a valid feature ID (org-level, present in `OrganizationModel.features` with `enabled: true` by default). `MembershipFeatureConfig.features` is now `Record<string, boolean>`, so `settings` can technically be overridden per member, but the convention is still to gate settings visibility via `requiresAdmin` on the nav entry and `AdminGuard` on routes rather than per-member feature overrides.
59
55
 
60
56
  **Subshell / Sidebar** -- the feature-scoped UI region rendered when the current route matches a manifest's `subshellRoutes`. Each `FeatureModule.sidebar` is a `ComponentType`. Consumers customize by composing the feature's published sidebar primitives (`CrmSidebar`, `CrmSidebarMiddle`, etc.) and assigning their component to `manifest.sidebar`.
61
57
 
62
- **Surface** -- a navigable view in `OrganizationModel.navigation.surfaces`. Identified by a dotted `id` (e.g., `crm.pipeline`). Has `path`, `surfaceType`, optional `featureKey` gate, and cross-reference arrays. Distinct from "page": a surface is the org-model declaration; a page is the React component rendered at the route.
58
+ **Surface** -- a navigable view in `OrganizationModel.navigation.surfaces`. Identified by a dotted `id` (e.g., `crm.pipeline`). Has `path`, `surfaceType`, optional `featureId` gate (replaces the former `featureKey` field), and cross-reference arrays (`featureIds`, `entityIds`, `resourceIds`, `capabilityIds`). Distinct from "page": a surface is the org-model declaration; a page is the React component rendered at the route.
63
59
 
64
60
  **Topology** -- resource relationships (`triggers`, `uses`, `approval`) declared in `DeploymentSpec.relationships` and `HumanCheckpointDefinition.routesTo`. Used for Command View graph edges.
65
61
 
@@ -69,7 +65,7 @@ This condensed version covers every ambiguity-prone term a template consumer or
69
65
 
70
66
  **`@elevasis/core`** (published npm)
71
67
 
72
- - `OrganizationModel`, `OrganizationModelFeatureKey`, `OrganizationModelSurface`, `OrganizationModelResourceMapping`
68
+ - `OrganizationModel`, `OrganizationModelFeature`, `OrganizationModelSurface`, `OrganizationModelResourceMapping`
73
69
  - `resolveOrganizationModel`, `defineOrganizationModel`, `DEFAULT_ORGANIZATION_MODEL`
74
70
  - `MembershipFeatureConfig`
75
71
 
@@ -79,7 +75,7 @@ This condensed version covers every ambiguity-prone term a template consumer or
79
75
  - `ResolvedFeatureModule`, `ResolvedShellNavItem`
80
76
  - `FeatureGuard`, `AdminGuard`, `ProtectedRoute`
81
77
  - `ElevasisFeaturesProvider`, `ElevasisCoreProvider`, `useElevasisFeatures`
82
- - `FEATURE_KEY_ALIASES`, `createFeatureAccessHook`
78
+ - `createFeatureAccessHook`
83
79
 
84
80
  **`foundations/`** (local source package -- not published)
85
81
 
@@ -113,7 +113,7 @@ const MyCrmSidebar = () => (
113
113
  const customCrmManifest = { ...crmManifest, sidebar: MyCrmSidebar }
114
114
  ```
115
115
 
116
- `PipelineHealthWidget` is a component you define in `ui/src/features/crm/` or `ui/src/shared/components/`. Keep route files and `__root.tsx` thin -- push component logic into feature modules.
116
+ `PipelineHealthWidget` is a component you define in `ui/src/features/crm/` or `ui/src/lib/components/`. Keep route files and `__root.tsx` thin -- push component logic into feature modules.
117
117
 
118
118
  ## Worked Example 3: Page Wrapping
119
119
 
@@ -127,7 +127,7 @@ The exported CRM page components are `DealsListPage` and `DealDetailPage`:
127
127
  import { createFileRoute } from '@tanstack/react-router'
128
128
  import { DealsListPage } from '@elevasis/ui/features/crm'
129
129
  import { ProtectedRoute } from '@/features/auth'
130
- import { ProjectAnnouncementBanner } from '@/shared/components/ProjectAnnouncementBanner'
130
+ import { ProjectAnnouncementBanner } from '@/lib/components/ProjectAnnouncementBanner'
131
131
  import { Stack } from '@mantine/core'
132
132
 
133
133
  export const Route = createFileRoute('/crm/deals/')({
@@ -8,8 +8,8 @@ description: End-to-end recipe for the three gating concepts -- featureKey nav v
8
8
  **Status: 🟢 Stable**
9
9
 
10
10
  This doc resolves the three-concept confusion that trips up most agents building new features. Read
11
- the overview table first, then follow the end-to-end chain for the template's actual grouped-key
12
- plus alias flow.
11
+ the overview table first, then follow the end-to-end chain for the template's unified feature ID
12
+ flow.
13
13
 
14
14
  ---
15
15
 
@@ -29,47 +29,32 @@ The three concepts work together but are independent. Hiding a nav item does not
29
29
 
30
30
  ## End-to-End Chain
31
31
 
32
- This section walks through the template's existing acquisition flow. In this scaffold, `crm` and
33
- `lead-gen` are route-level aliases that map to the grouped org-model key `acquisition`.
32
+ This section walks through the template's feature gating flow. Features are declared as objects in
33
+ the org model with a direct `id` field. `FeatureModule.featureId` matches that `id` exactly -- no
34
+ alias layer exists.
34
35
 
35
- If you need a brand-new shell feature key such as `analytics`, that is not a template-only change.
36
- It requires updating the published `@elevasis/core` organization-model contract and any shared
37
- `@elevasis/ui` feature manifests that should consume it.
36
+ If you need a brand-new feature such as `analytics`, add a feature object to the `features[]`
37
+ defaults array in the org model and author a matching manifest. See [add-a-feature.md](../recipes/add-a-feature.md).
38
38
 
39
- ### Step 1: Declare the grouped org-model gate in `organization-model.ts`
39
+ ### Step 1: Declare the feature in `organization-model.ts`
40
40
 
41
41
  File: `foundations/config/organization-model.ts`
42
42
 
43
43
  ```ts
44
44
  const foundationOrganizationModelOverride = defineOrganizationModel({
45
- features: {
46
- enabled: {
47
- acquisition: true
48
- },
49
- labels: {
50
- acquisition: 'Acquisition'
51
- }
52
- },
45
+ features: [
46
+ { id: 'crm', label: 'CRM', enabled: true, entityIds: [...], surfaceIds: [...] },
47
+ { id: 'lead-gen', label: 'Lead Gen', enabled: true, entityIds: [...], surfaceIds: [...] },
48
+ { id: 'projects', label: 'Projects', enabled: true, entityIds: [...], surfaceIds: [...] },
49
+ // ... other features
50
+ ],
53
51
  // ... rest of model
54
52
  })
55
53
  ```
56
54
 
57
- The `features.enabled` record is the source of truth for the published org-model gates. In the
58
- current template, those keys are the grouped platform keys:
59
- `acquisition`, `delivery`, `operations`, `monitoring`, `settings`, `seo`, and `calibration`.
60
-
61
- Further down in the same file, the template adapts that canonical model into shell-friendly aliases:
62
-
63
- ```ts
64
- const featuresEnabled: Record<FoundationFeatureKey, boolean> = {
65
- acquisition: resolvedOrganizationModel.features.enabled.acquisition,
66
- crm: resolvedOrganizationModel.features.enabled.acquisition,
67
- 'lead-gen': resolvedOrganizationModel.features.enabled.acquisition
68
- }
69
- ```
70
-
71
- That alias layer is why route guards can ask for `crm` or `lead-gen` even though the published
72
- organization-model contract only knows about `acquisition`.
55
+ The `features[]` array is the source of truth for org-model gates. The 7 default feature IDs are
56
+ `crm`, `lead-gen`, `projects`, `operations`, `monitoring`, `settings`, and `seo`. Each feature
57
+ object's `enabled` field controls whether that feature is on by default for the org.
73
58
 
74
59
  ### Step 2: Wire `featureKey` on a nav item
75
60
 
@@ -85,10 +70,11 @@ import { crmManifest } from '@elevasis/ui/features/crm'
85
70
  const manifest: FeatureModule = crmManifest
86
71
  ```
87
72
 
88
- `accessFeatureKey` on the `FeatureModule` is what drives whether the entire shared feature
89
- (including its nav entry) is visible. In the published manifests, `crm` and `lead-gen` both use
90
- `accessFeatureKey: 'acquisition'`; `delivery` uses `accessFeatureKey: 'delivery'`. The shell hides
91
- the nav entry when org or membership access disables that grouped key.
73
+ `featureId` on the `FeatureModule` is what drives whether the entire shared feature (including its
74
+ nav entry) is visible. Each manifest sets `featureId` to the matching `feature.id` value from the
75
+ org model (e.g., `crmManifest.featureId = 'crm'`, `leadGenManifest.featureId = 'lead-gen'`,
76
+ `projectsManifest.featureId = 'projects'`). The shell hides the nav entry when org or membership
77
+ access disables that feature ID.
92
78
 
93
79
  **Local nav-items.ts** (for one-off items not part of a registered feature):
94
80
 
@@ -101,8 +87,8 @@ export const navItems: ExtendedLinksGroupProps[] = [
101
87
  ```
102
88
 
103
89
  When `featureKey` is set on a nav item in `nav-items.ts`, the shell hides that item if
104
- `isFeatureEnabled(featureKey)` returns false. Template-local aliases like `crm`, `lead-gen`, and
105
- `projects` work here because the adapted `organizationModel` exposed by foundations includes them.
90
+ `isFeatureEnabled(featureKey)` returns false. The key must match a `feature.id` value in the org
91
+ model -- no alias mapping is applied.
106
92
 
107
93
  ### Step 3: Wrap the route in `FeatureGuard`
108
94
 
@@ -141,7 +127,7 @@ All existing feature routes in the template follow this exact pattern: `operatio
141
127
  For conditional UI within a component (showing or hiding a button, panel, or section based on a feature flag), use the `useFeatureAccess` hook directly.
142
128
 
143
129
  ```tsx
144
- import { useFeatureAccess } from '@/shared/hooks/useFeatureAccess'
130
+ import { useFeatureAccess } from '@/lib/hooks/useFeatureAccess'
145
131
 
146
132
  function Dashboard() {
147
133
  const { hasFeature } = useFeatureAccess()
@@ -157,9 +143,9 @@ function Dashboard() {
157
143
 
158
144
  `hasFeature` returns a boolean. Use it for render-conditional logic only. Do not use it as a substitute for `FeatureGuard` on a route -- hiding a component does not prevent direct URL access.
159
145
 
160
- The hook is defined in `ui/src/shared/hooks/useFeatureAccess.ts`. It wraps
161
- `createFeatureAccessHook` from `@elevasis/ui/hooks` and extends it with template-specific key
162
- aliases (`crm` and `lead-gen` alias to `acquisition`; `projects` aliases to `delivery`).
146
+ The hook is defined in `ui/src/lib/hooks/useFeatureAccess.ts`. It wraps
147
+ `createFeatureAccessHook` from `@elevasis/ui/hooks` and resolves feature IDs directly against the
148
+ org model's `features[]` array -- no alias mapping is applied.
163
149
 
164
150
  ---
165
151
 
@@ -235,24 +221,24 @@ Use this pattern when only a section of a page is admin-only, not the entire rou
235
221
 
236
222
  ## Import Paths
237
223
 
238
- | Symbol | Import path | Notes |
239
- | ------------------------- | ---------------------------------------- | -------------------------------------------------------------------------------------- |
240
- | `FeatureGuard` | `@/features/auth/guards/FeatureGuard` | Template-local wrapper; uses `useFeatureAccess` from `@/shared/hooks/useFeatureAccess` |
241
- | `AdminGuard` | `@elevasis/ui/auth` | Published from `@elevasis/ui` |
242
- | `useFeatureAccess` | `@/shared/hooks/useFeatureAccess` | Template-local wrapper around `createFeatureAccessHook` from `@elevasis/ui/hooks` |
243
- | `createFeatureAccessHook` | `@elevasis/ui/hooks` | Factory for building a feature-access hook; already consumed by the template wrapper |
244
- | `ProtectedRoute` | `@elevasis/ui/auth` or `@/features/auth` | Ensures user is authenticated before any guard runs |
224
+ | Symbol | Import path | Notes |
225
+ | ------------------------- | ---------------------------------------- | ------------------------------------------------------------------------------------ |
226
+ | `FeatureGuard` | `@/features/auth/guards/FeatureGuard` | Template-local wrapper; uses `useFeatureAccess` from `@/lib/hooks/useFeatureAccess` |
227
+ | `AdminGuard` | `@elevasis/ui/auth` | Published from `@elevasis/ui` |
228
+ | `useFeatureAccess` | `@/lib/hooks/useFeatureAccess` | Template-local wrapper around `createFeatureAccessHook` from `@elevasis/ui/hooks` |
229
+ | `createFeatureAccessHook` | `@elevasis/ui/hooks` | Factory for building a feature-access hook; already consumed by the template wrapper |
230
+ | `ProtectedRoute` | `@elevasis/ui/auth` or `@/features/auth` | Ensures user is authenticated before any guard runs |
245
231
 
246
- The template's `FeatureGuard` (`@/features/auth/guards/FeatureGuard`) is not the same as the published `FeatureGuard` from `@elevasis/ui/features`. The template version closes over the local `useFeatureAccess` hook and the local organization model labels, so it knows your feature key aliases. Use the template-local version.
232
+ The template's `FeatureGuard` (`@/features/auth/guards/FeatureGuard`) is not the same as the published `FeatureGuard` from `@elevasis/ui/features`. The template version closes over the local `useFeatureAccess` hook and the resolved org model, so it checks feature IDs against your project's feature array. Use the template-local version.
247
233
 
248
234
  ---
249
235
 
250
236
  ## Common Mistakes
251
237
 
252
- - **Inventing a new org-model key in the template only.** The published
253
- `OrganizationModelFeatureKey` union is fixed in `@elevasis/core`. Template-local aliases like
254
- `crm`, `lead-gen`, and `projects` work because foundations maps them onto grouped keys; a brand
255
- new shell key such as `analytics` requires a core/package contract change, not just a doc tweak.
238
+ - **Using a feature ID that does not exist in the org model.** `FeatureModule.featureId` and
239
+ `featureKey` on nav entries must match a `feature.id` value in the org model's `features[]`
240
+ array. The provider throws at startup if an unknown ID is declared. To add a brand-new feature
241
+ such as `analytics`, first add it to the `features[]` array in `foundations/config/organization-model.ts`.
256
242
 
257
243
  - **Using `FeatureGuard` on a nav item instead of a route.** `FeatureGuard` is a React component that redirects on mount. Placing it inside a nav item or link will fire a redirect whenever the sidebar renders. Gate nav visibility with `featureKey` on the nav entry or `accessFeatureKey` on the manifest; gate route access with `FeatureGuard` in the route layout.
258
244
 
@@ -10,10 +10,10 @@ Within Organization OS, the feature shell spans two layers: the **UI Shell Runti
10
10
  Three layers participate, and the word "feature" means something different in each:
11
11
 
12
12
  - **Platform capabilities** -- product-facing areas documented under `technical/features/` (Execution Engine, Workflows, Agents, Operations, etc.). These are not one-to-one with shell features.
13
- - **Shell features** -- features in `packages/ui/src/features/*`. Seven are manifest-backed (`lead-gen`, `crm`, `delivery`, `operations`, `monitoring`, `settings`, `seo`); two are utility features without manifests (`auth`, `dashboard`).
14
- - **Organization-model features** -- grouped semantic/access keys in `@repo/core/organization-model` (`acquisition`, `delivery`, `operations`, `monitoring`, `settings`, `seo`, `calibration`). See [Organization Model](../core/organization-model.mdx).
13
+ - **Shell features** -- features in `packages/ui/src/features/*`. Seven are manifest-backed (`crm`, `lead-gen`, `projects`, `operations`, `monitoring`, `settings`, `seo`); two are utility features without manifests (`auth`, `dashboard`).
14
+ - **Organization-model features** -- feature objects in `@repo/core/organization-model` with shape `{ id, label, enabled, entityIds, surfaceIds, resourceIds, capabilityIds }`. The 7 defaults are `crm`, `lead-gen`, `projects`, `operations`, `monitoring`, `settings`, `seo`. See [Organization Model](../core/organization-model.mdx).
15
15
 
16
- One shell feature can contain several platform capabilities; shell and organization-model keys are different concepts.
16
+ One shell feature can contain several platform capabilities; a manifest's `featureId` directly matches `feature.id` in the org model with no alias layer.
17
17
 
18
18
  ## Source of Truth
19
19
 
@@ -30,24 +30,24 @@ One shell feature can contain several platform capabilities; shell and organizat
30
30
 
31
31
  A `FeatureModule` describes one shell feature's contribution. Fields:
32
32
 
33
- - `key` -- unique stable identifier (e.g., `'crm'`, `'delivery'`)
34
- - `accessFeatureKey` -- **required**; the key `useFeatureAccess()` checks for gating
35
- - `domainIds`, `capabilityIds` -- semantic references into the organization model
33
+ - `key` -- unique stable identifier (e.g., `'crm'`, `'projects'`)
34
+ - `featureId` -- **required**; the `feature.id` value from the org model that this module maps to; used by `useFeatureAccess()` for gating
35
+ - `capabilityIds` -- semantic references into the organization model
36
36
  - `navEntry` -- top-level nav contribution (label, icon, optional `link`, optional nested `links`, optional `requiresAdmin`, optional onboarding-tour ID, optional `featureKey` override when nav identity must diverge from access identity)
37
37
  - `sidebar` -- optional `ComponentType` for the feature's subshell sidebar
38
38
  - `subshellRoutes` -- routes the feature owns under its subshell
39
39
  - `organizationGraph` -- **Operations-only**; bridges a manifest to an organization-model surface (ignored by other features)
40
40
 
41
- `FeatureModule.label` has been removed; nav labels resolve from `navEntry.label` or from organization-model feature labels.
41
+ `FeatureModule.label` has been removed; nav labels resolve from `navEntry.label` or from the matching organization-model feature's `label` field.
42
42
 
43
- Manifests are validated at provider registration via `validateManifests()`. Unknown `accessFeatureKey`, `domainIds`, or `capabilityIds` (measured against the resolved organization model) throw with all violations collected into one error.
43
+ Manifests are validated at provider registration via `validateManifests()`. Unknown `featureId` or `capabilityIds` (measured against the resolved organization model) throw with all violations collected into one error.
44
44
 
45
45
  ### ResolvedFeatureModule
46
46
 
47
47
  `ResolvedFeatureModule` extends `FeatureModule` with two additional fields added during provider resolution:
48
48
 
49
- - `access: ResolvedFeatureAccess` -- `{ featureKey: string, enabled: boolean }` -- the resolved access state for this feature
50
- - `semantics: ResolvedFeatureSemantics` -- `{ domainIds, capabilityIds, surfaceIds, surfaces }` -- merged semantic identifiers derived from both manifest declarations and organization-model surface data
49
+ - `access: ResolvedFeatureAccess` -- `{ featureId: string, enabled: boolean }` -- the resolved access state for this feature
50
+ - `semantics: ResolvedFeatureSemantics` -- `{ capabilityIds, surfaceIds, surfaces }` -- merged semantic identifiers derived from both manifest declarations and organization-model surface data
51
51
 
52
52
  `ResolvedFeatureSemantics.surfaces` carries the full `OrganizationModelSurface[]` objects (not just IDs), so consumers can inspect surface metadata without a separate lookup.
53
53
 
@@ -108,8 +108,8 @@ Consumers keep thin local route wrappers and app-local nav where needed.
108
108
 
109
109
  1. The app defines a manifest list (often by spreading `FEATURE_MANIFESTS` and overriding entries).
110
110
  2. The app optionally resolves an organization model and passes it to the provider.
111
- 3. The provider combines `useFeatureAccess()` with organization-model feature state to compute enabled features.
112
- 4. Nav labels and nav paths resolve from `organizationModel.features.labels` and `organizationModel.navigation.surfaces` when present.
111
+ 3. The provider combines `useFeatureAccess()` with the org model's `features[]` array to compute enabled features.
112
+ 4. Nav labels and nav paths resolve from the matching `feature.label` in `organizationModel.features` and from `organizationModel.navigation.surfaces` when present.
113
113
  5. The provider exposes `shellModel`, `shellRuntime`, resolved feature access, `organizationGraph`, and shared runtime inputs via `useElevasisFeatures()`.
114
114
  6. `FeatureShell` calls `shellRuntime.resolveRoute(currentPath)`:
115
115
  - `matched` -- render the feature's sidebar subshell
@@ -144,7 +144,7 @@ Consumer nav derivation runs locally from `shellModel.navItems`; the provider no
144
144
 
145
145
  - `placement: 'primary' | 'bottom'` -- where the item appears in the shell nav; feature manifests always produce `'primary'`; `appShellOverrides.bottomNavItems` produce `'bottom'`
146
146
  - `source: 'app' | 'feature'` -- whether the item came from a manifest (`'feature'`) or from `appShellOverrides` (`'app'`)
147
- - `accessFeatureKey?: string` -- the feature key associated with this nav item for gating checks
147
+ - `featureId?: string` -- the feature ID associated with this nav item for gating checks
148
148
 
149
149
  `shellModel.navItems` contains the merged and filtered list of all `ResolvedShellNavItem`s from both manifest features and `appShellOverrides`.
150
150
 
@@ -152,14 +152,14 @@ Consumer nav derivation runs locally from `shellModel.navItems`; the provider no
152
152
 
153
153
  `filterNavLinks()` in `ElevasisFeaturesProvider.tsx` recursively removes gated and disabled-subsection links from a `FeatureNavLink[]` array. A link is removed if its `featureKey` fails `isFeatureEnabled()` or if its path matches any entry in `disabledSubsectionPaths`. The function recurses into nested `links` arrays before deciding whether a parent with now-empty children should be retained or dropped.
154
154
 
155
- ## Access Resolution & Key Aliasing
155
+ ## Access Resolution
156
156
 
157
- Shell feature-module keys (`crm`, `lead-gen`, `delivery`) differ from the organization-model grouped keys used by published membership config (`acquisition`, `delivery`). The published `@elevasis/ui` line preserves grouped-key compatibility:
157
+ Shell feature-module keys map directly to feature IDs in the organization model. There is no alias layer:
158
158
 
159
159
  - `createFeatureAccessHook` is the factory that produces `useFeatureAccess`. (Old name `createUseFeatureAccess` is re-exported as `@deprecated`.)
160
- - `FEATURE_KEY_ALIASES` in `packages/ui/src/hooks/feature-access/aliases.ts` is the single authoritative alias map. `crm -> acquisition`, `lead-gen -> acquisition`, `projects -> delivery`.
161
- - Shared manifests declare grouped `accessFeatureKey` (e.g., `crmManifest.accessFeatureKey = 'acquisition'`) so the published contract gates against `MembershipFeatureConfig` as written.
162
- - The provider fallback for missing feature access identity is retired -- explicit `accessFeatureKey` is required.
160
+ - `FeatureModule.featureId` must match a `feature.id` value in the org model's `features[]` array. The provider throws at startup if the ID is not found.
161
+ - `MembershipFeatureConfig.features` is `Record<string, boolean>` -- a dynamic map keyed by feature ID. Any feature can be overridden per member without schema changes.
162
+ - The provider fallback for missing feature access identity is retired -- explicit `featureId` is required.
163
163
 
164
164
  ## Published Surface
165
165
 
@@ -170,8 +170,8 @@ All nine features are published as individual subpath exports from `@elevasis/ui
170
170
  - `@elevasis/ui/features/auth` -- `ProtectedRoute`, `AdminGuard`, `FeatureGuard`, `useUserProfile` (utility feature; no manifest)
171
171
  - `@elevasis/ui/features/dashboard` -- `Dashboard`, `ResourceOverview`, `RecentExecutionsByResource`, `UnresolvedErrorsTeaser` (utility feature; no manifest)
172
172
  - `@elevasis/ui/features/crm`
173
- - `@elevasis/ui/features/delivery`
174
173
  - `@elevasis/ui/features/lead-gen`
174
+ - `@elevasis/ui/features/projects`
175
175
  - `@elevasis/ui/features/monitoring`
176
176
  - `@elevasis/ui/features/operations`
177
177
  - `@elevasis/ui/features/seo`
@@ -201,7 +201,7 @@ The `auth` and `dashboard` features are not in `FEATURE_MANIFESTS` and are not r
201
201
 
202
202
  Both arrays go through the same `isFeatureEnabled()` and `disabledSubsectionPaths` filtering as manifest-derived items. Items whose `featureKey` is disabled, or that have only disabled nested links and no direct `link`, are dropped entirely.
203
203
 
204
- Nine feature modules are published (see **Published Surface** above). Six are currently mounted in command-center: `leadGenManifest`, `crmManifest`, `deliveryManifest`, `operationsManifest`, `monitoringManifest`, `settingsManifest`. The remaining three (`seoManifest`, `auth`, `dashboard`) are published but not mounted through `ElevasisFeaturesProvider` in command-center -- `auth` and `dashboard` are utility features consumed directly, while `seoManifest` is available for composition but not wired into the current command-center shell.
204
+ Nine feature modules are published (see **Published Surface** above). Six are currently mounted in command-center: `leadGenManifest`, `crmManifest`, `projectsManifest`, `operationsManifest`, `monitoringManifest`, `settingsManifest`. The remaining three (`seoManifest`, `auth`, `dashboard`) are published but not mounted through `ElevasisFeaturesProvider` in command-center -- `auth` and `dashboard` are utility features consumed directly, while `seoManifest` is available for composition but not wired into the current command-center shell.
205
205
 
206
206
  ### External-consumer composition
207
207
 
@@ -221,7 +221,6 @@ The CRM sidebar (`packages/ui/src/features/crm/sidebar/`) exports `CrmSidebar`,
221
221
 
222
222
  - Returns `null` for sessions (`/operations/sessions`) and command-view (`/operations/command-view`) routes -- these sections own their own top-area UI
223
223
  - Shows a "Resource Overview" navigation button for the resources section (`/operations/resources`)
224
- - Shows a "Calibration Projects" navigation button for the calibration section (`/operations/calibration`)
225
224
  - Returns `null` for all other paths (e.g., the operations index)
226
225
 
227
226
  This pattern avoids hardcoding sidebar top content in the parent and lets each operations sub-section declare its own top panel entry point.
@@ -236,6 +235,6 @@ This pattern avoids hardcoding sidebar top content in the parent and lets each o
236
235
  ## Key Conceptual Distinctions
237
236
 
238
237
  - **Platform capability vs. shell feature** -- capabilities are the product map; shell features are manifest-backed UI surfaces.
239
- - **Shell feature key vs. organization-model feature key** -- `crm` (shell) vs. `acquisition` (published access identity).
240
- - **Semantic domain vs. feature key** -- `domain = crm` describes the business area; `feature key = acquisition` gates access.
238
+ - **Shell feature key vs. org-model feature id** -- both are the same value (e.g., `crm`). `FeatureModule.featureId` directly matches `feature.id` in the org model; there is no alias layer.
239
+ - **Feature vs. capability** -- a feature object in the org model carries `entityIds`, `surfaceIds`, `resourceIds`, and `capabilityIds`; capabilities are one kind of semantic reference a feature can hold.
241
240
  - **Provider-owned vs. consumer-owned** -- provider owns shared manifests, nav contribution, sidebar dispatch, shared runtime context; consumers own route files, branding, topbar behavior, admin entries.