@elevasis/sdk 1.52.0 → 1.52.1

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 CHANGED
@@ -49330,7 +49330,7 @@ var init_package = __esm({
49330
49330
  "package.json"() {
49331
49331
  package_default = {
49332
49332
  name: "@elevasis/sdk",
49333
- version: "1.52.0",
49333
+ version: "1.52.1",
49334
49334
  description: "SDK for building Elevasis organization resources",
49335
49335
  type: "module",
49336
49336
  bin: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elevasis/sdk",
3
- "version": "1.52.0",
3
+ "version": "1.52.1",
4
4
  "description": "SDK for building Elevasis organization resources",
5
5
  "type": "module",
6
6
  "bin": {
@@ -64,8 +64,8 @@
64
64
  "vitest": "^3.2.4",
65
65
  "zod": "^4.1.0",
66
66
  "@repo/core": "0.69.0",
67
- "@repo/typescript-config": "0.0.0",
68
- "@repo/eslint-config": "0.0.0"
67
+ "@repo/eslint-config": "0.0.0",
68
+ "@repo/typescript-config": "0.0.0"
69
69
  },
70
70
  "scripts": {
71
71
  "lint": "eslint src --max-warnings 0",
@@ -295,9 +295,9 @@ Docs-site pages indexed: 38.
295
295
  | Getting Started | `sdk/getting-started.mdx` | Set up your Elevasis SDK project and run your first deployment |
296
296
  | Human-in-the-Loop (HITL) Workflows | `sdk/human-in-the-loop.mdx` | How a workflow step opens an approval task, how it reaches the command queue, and how selecting an action resumes work -- the story that connects the approval adapter, checkpoint metadata, and the queue CLI. |
297
297
  | @elevasis/sdk | `sdk/index.mdx` | Build and deploy workflows, agents, and resources with the Elevasis SDK |
298
- | Integration Adapters | `sdk/platform-tools/adapters-integration.mdx` | Auto-generated table of all 13 integration (credential-bound) adapters exported from @elevasis/sdk/worker, derived from static analysis of the adapter source files. |
299
- | Platform Adapters | `sdk/platform-tools/adapters-platform.mdx` | Auto-generated table of all 14 platform (singleton, no credential) adapters exported from @elevasis/sdk/worker, derived from static analysis of the adapter source files. |
300
- | Platform Tools | `sdk/platform-tools/index.mdx` | Access 25 adapters (13 integration + 12 platform) from your SDK workflows -- typed adapters, credential security model, and working code examples |
298
+ | Integration Adapters | `sdk/platform-tools/adapters-integration.mdx` | Auto-generated table of all 14 integration (credential-bound) adapters exported from @elevasis/sdk/worker. Each row's methods are read from that adapter's METHODS array; the adapter set is the INTEGRATION_EXPORTS list in the generator, checked against the adapter directory on every run. |
299
+ | Platform Adapters | `sdk/platform-tools/adapters-platform.mdx` | Auto-generated table of all 14 platform (singleton, no credential) adapters exported from @elevasis/sdk/worker. Each row's methods are read from that adapter's METHODS array; the adapter set is the PLATFORM_EXPORTS list in the generator, checked against the adapter directory on every run. |
300
+ | Platform Tools | `sdk/platform-tools/index.mdx` | Access 28 adapters (14 integration + 14 platform) from your SDK workflows -- typed adapters, credential security model, and working code examples |
301
301
  | Adapter Type Safety | `sdk/platform-tools/type-safety.mdx` | SDK worker adapter type safety patterns - required fields, discriminated unions, and intentionally loose types |
302
302
  | The Deployment Spec Pattern | `sdk/project-deployment-spec.mdx` | How projectDeploymentSpec and defineWorkflowConfig assemble the DeploymentSpec a scaffolded project actually ships, using operations/src/index.ts as the reference. |
303
303
  | Writing Resources | `sdk/resources/index.mdx` | Guide to creating descriptor-backed workflows and agents with the Elevasis SDK |
@@ -10,11 +10,14 @@ paths:
10
10
 
11
11
  # Content
12
12
 
13
- - **These routes render and do nothing until the System behind them is built.** Unlike CRM and lead-gen, `content` has no entry yet in `core/config/organization-model/systems.ts` -- the routes gate on `accessKey="content"` with no System, ontology, or resources behind it on a fresh project.
13
+ - **These routes render and do nothing until the System behind them is adopted.** `content` **does** have an entry in `core/config/organization-model/systems.ts`, alongside CRM and lead-gen. Do not add a second one. The entry ships `enabled: true` with `lifecycle: 'draft'`, and that draft state is the deliberate opt-out, not a half-finished System: with no `apiInterface`, the shared Content pages have no API surface to call. Adoption means flipping `lifecycle` to `'active'` and adding an `apiInterface` once this project has real content workflows -- `defineContentSystem` throws on empty `resources`, which is exactly why the template does not call it.
14
14
  - **One recipe covers this surface and no other rule names it.** `extend-content.md` covers the System, its pipeline/step/status/pillar/platform catalogs, and the `content` workflow adapter. Read it before authoring.
15
15
  - **A pipeline is data, not code.** Pipelines and their ordered steps are catalog records (`content:catalog/pipeline`, `content:catalog/{pipelineId}-steps`); adding one is an org-model edit plus one workflow per step, never a shared-UI or route change.
16
16
  - **Pipeline, step, status, and pillar vocabulary is model-owned, never a local constant.** Copying catalog entries into a `const` is architecturally excluded -- every project owns its own model, so the copy rots silently. Read catalog entries from the resolved model.
17
17
  - **`apiInterface` is adopt-only here.** Content uses the flat `system.apiInterface` marker on the `content` System path; readiness is derived from scoped resources, ontology bindings, and required catalogs -- never invent a readiness profile.
18
+ - **Every step records an attempt when it advances -- human steps included.** `content_items.processing_state` is read-only and rebuilt from that item's attempts on every attempt write; there is no `updateProcessingState` anywhere in the stack, so **writing an attempt is the only way to move an item**. An agent or workflow step satisfies this for free, because the execution behind it writes `running` and patches the terminal status. A **human** step has no execution and therefore no producer, so the surface that advances it is the only thing that can write its attempt. A panel that moves the operator on with a bare client-side stepper call records nothing: the step never reaches `'success'`, and board placement pins the item to that column however far it actually goes. Nothing enforces this -- no gate, no type -- which is why it is stated here. It reached two tenants at once purely because the rule lived in the board-derivation implementation and nowhere a panel author would look.
19
+ - **Two shapes satisfy that contract.** **Inline** -- the step writes its own attempt as it advances; available to any human step reached with an item in hand, and the only shape most projects need. **Backfilled at creation** -- a pipeline that deliberately defers the first write (so an abandoned selection leaves no rows behind) runs its first human steps with no item to write against, so those attempts go in one pass right after the item is created, derived from wherever creation lands rather than listed per pipeline. Both write `payload: {}` and `sourceExecutionId: null` -- a person doing the step is the result, and there is no execution to name. Guard the write on an existing `'success'` entry so walking back and forward does not pile up rows, and never let a failed write block the operator: the step's work is genuinely done, and the next walk through repairs the projection.
20
+ - **A review is an attempt too.** A human clearing a gate writes an attempt on the step being cleared, via `content_items/:id/review` -- which resolves the exact step through the open-review-gate query rather than guessing. Do not widen `content_items` with per-gate columns. A review attempt legitimately has `sourceExecutionId: null` (a judgment call has no execution) and carries the reviewer's identity in `createdBy`. That route is only reachable for a step an execution actually produced: `getOpenContentReviewGates` resolves gates through `executionId`, so a human-actor step with no `action` and no `resource` structurally can never have an open gate to clear. For those steps the inline attempt above is not the preferred shape, it is the only one.
18
21
 
19
22
  ## Related Rules
20
23
 
@@ -16,7 +16,7 @@ paths:
16
16
  - `routeTree.gen.ts` is auto-generated on `pnpm dev` -- never edit manually
17
17
  - Auth protection: wrap page content with `ProtectedRoute` from `@elevasis/ui/features/auth`. Gate further with `AccessGuard accessKey={...}` nested inside `ProtectedRoute` -- a System path for system gating, or an `AccessKeys` constant (e.g. `AccessKeys.platformAdmin`) for permission-backed gating. `FeatureGuard`, `SystemGuard`, `SurfaceGuard`, and `AdminGuard` are retired -- do not reintroduce them
18
18
  - **For a System's top-level route, use `ProtectedSystemRoute accessKey={...}` from `@elevasis/ui/features/auth` instead of writing that pairing by hand.** It expands to exactly the `ProtectedRoute` + `AccessGuard` nesting above and adds `SystemUnavailableState` as the guard's `fallback`, so a denied user sees the reason rather than `AccessGuard`'s blank `fallback = null` default. Every System route in the template ships this shape; see `ui.md` "System route guarding". Keep `createFileRoute` in the route file and keep bare `AccessGuard` for gating below the route level
19
- - **Two `ProtectedRoute` components exist and the subpath you import from decides what the user sees while the app initializes.** `@elevasis/ui/features/auth` is the one route files want: it wraps the base guard with an animated full-screen loader and a default `AppShellError` error fallback. `@elevasis/ui/auth` exports the headless base guard, whose `fallback` defaults to `null` -- importing it without passing a `fallback` renders a blank screen for the whole initialization window and swallows initialization errors. Reach for the headless one only when the surface renders its own loading state. `AccessKeys` is a plain constant table and stays on `@elevasis/ui/auth` (or `@elevasis/ui/hooks`) either way
19
+ - **Two `ProtectedRoute` components exist and the subpath you import from decides what the user sees while the app initializes.** `@elevasis/ui/features/auth` is the one route files want: it wraps the base guard with an animated full-screen loader and a default `ServiceUnavailableScreen` error fallback, which classifies the initialization failure and retries on a backoff. `@elevasis/ui/auth` exports the headless base guard, whose `fallback` defaults to `null` -- importing it without passing a `fallback` renders a blank screen for the whole initialization window and swallows initialization errors. Reach for the headless one only when the surface renders its own loading state. `AccessKeys` is a plain constant table and stays on `@elevasis/ui/auth` (or `@elevasis/ui/hooks`) either way
20
20
  - Never fork `@elevasis/ui` components -- if a published component needs a tweak, that missing capability is a bug in `@elevasis/ui`
21
21
 
22
22
  ## Silent-Break Gotchas
@@ -52,4 +52,4 @@ When building pages that display external data, use published `@elevasis/ui` com
52
52
  - `operations/node_modules/@elevasis/sdk/reference/scaffold/ui/customization.md` -- sidebar composition via manifest overrides
53
53
  - `operations/node_modules/@elevasis/sdk/reference/scaffold/reference/contracts.md` -- TypeScript shapes (`SystemModule`, `NavItem`, `OrganizationModel`)
54
54
  - `ui/src/config/theme.ts` -- theme configuration and CSS variable definitions
55
- - `ui/src/config/nav-items.ts` -- sidebar navigation entries
55
+ - `ui/src/config/shell.tsx` -- the `SYSTEM_MANIFESTS` array of mounted System modules, and the seam for sidebar overrides
@@ -17,7 +17,7 @@ Organization OS is the semantic contract layer defining how organizations, Syste
17
17
  - `core/config/extensions/` -- project-owned entity extension schemas
18
18
  - `core/types/entities.ts` -- typed entity contracts (Project, Deal, etc.) extending `BaseProject` / `BaseDeal` from `@elevasis/core/entities`. Reference these from workflow input schemas -- do not redeclare them.
19
19
  - `ui/src/routes/__root.tsx` -- wires `ElevasisSystemsProvider` with `canonicalOrganizationModel`
20
- - `ui/src/app-config.ts` -- references the org model
20
+ - `ui/src/config/app-config.ts` -- references the org model
21
21
  - `operations/src/index.ts` -- `DeploymentSpec` registry for workflows and agents
22
22
 
23
23
  ## Domain Overview
@@ -18,18 +18,20 @@ The template frontend is a React 19 + TanStack Router app that composes a local
18
18
  The main join points are:
19
19
 
20
20
  - `ui/src/main.tsx` -- boots the app with `ElevasisUIProvider`, query client, theme config, WorkOS AuthKit, notifications, and the generated route tree
21
- - `ui/src/routes/__root.tsx` -- composes the authenticated shell with `ElevasisSystemsProvider`, published System modules, app-local dashboard nav, shell runtime dependencies, and `SystemShell`
22
- - `ui/src/config/nav-items.ts` -- keeps the host-local dashboard entry separate from the published feature manifests
21
+ - `ui/src/config/shell.tsx` -- **the file you own for shell composition.** Declares the `SYSTEM_MANIFESTS` array of published System modules the shell mounts. Add, remove, or swap a System module here
22
+ - `ui/src/routes/__root.tsx` -- composes the authenticated shell: imports `SYSTEM_MANIFESTS` from `@/config/shell`, filters it through `selectDeclaredSystems` against `canonicalOrganizationModel`, and passes the result plus shell runtime dependencies into `ElevasisSystemsProvider` / `SystemShell`. This file is sync-managed -- edit `shell.tsx` instead
23
23
  - `core/config/organization-model.ts` -- is the template's semantic source of truth, adapting `@elevasis/core/organization-model` into the preserved branding, dashboard label, quick-access, System labels, resource descriptors, and shell helpers
24
24
 
25
- Published System modules mounted by the template shell:
25
+ Published System modules mounted by the template shell, in `ui/src/config/shell.tsx`'s `SYSTEM_MANIFESTS` order:
26
26
 
27
- - `lead-gen`
28
- - `crm`
29
- - `delivery` at `/projects`
30
- - `operations`
31
- - `monitoring`
32
- - `settings`
27
+ - `leadGenManifest` -- lead-gen
28
+ - `crmManifest` -- CRM
29
+ - `deliveryManifest` -- delivery, at `/projects`
30
+ - `operationsManifest` -- operations
31
+ - `submittedRequestsManifest` -- submitted requests, from `@elevasis/ui/features/monitoring/requests`
32
+ - `knowledgeManifest` -- knowledge
33
+
34
+ There is no `settings` manifest and no top-level `monitoring` manifest; settings routes are template-owned and the monitoring surface reaches the shell only through `submittedRequestsManifest`. Read `shell.tsx` rather than this list if the two disagree -- that file is the source of truth.
33
35
 
34
36
  Important distinction:
35
37
 
@@ -42,7 +44,7 @@ Dashboard remains a host-local route at `/`, not a shared feature manifest.
42
44
  This template should be treated as the downstream reference implementation for this composition:
43
45
 
44
46
  - `core/config/organization-model.ts` owns the organization/runtime semantics
45
- - `ui/src/config/nav-items.ts` preserves the host-local dashboard entry instead of pushing that concern into shared manifests
47
+ - `ui/src/config/shell.tsx` owns which published System modules are mounted, keeping that choice project-local instead of pushing it into `__root.tsx`
46
48
  - `ui/src/routes/__root.tsx` threads `canonicalOrganizationModel` from `@core/config/organization-model` into `ElevasisSystemsProvider` so the shared shell/runtime uses the same semantic source of truth as the local template helpers
47
49
  - host-local customizations still stay local: dashboard remains app-owned nav, branding stays in app config, and quick-access/dashboard UX stays in the template app
48
50
 
@@ -62,7 +64,7 @@ The app uses WorkOS AuthKit through `ElevasisUIProvider`. Authentication is enfo
62
64
 
63
65
  Wrap protected route components with `ProtectedRoute` from `@elevasis/ui/features/auth`. For a **System's top-level route**, use `ProtectedSystemRoute` instead -- it is the canonical shape and is what `ui/src/routes/crm.tsx`, `lead-gen.tsx`, `monitoring.tsx`, `operations.tsx`, `projects.tsx`, `content.tsx`, and `knowledge.tsx` all ship as. See "System route guarding" below. `ProtectedRoute` on its own is for a protected route that gates on authentication alone:
64
66
 
65
- `@elevasis/ui` publishes two components under this name and they behave differently. The one route files want is `@elevasis/ui/features/auth`: it wraps the base guard with an animated full-screen loader that fades out when initialization completes, and supplies a default `AppShellError` fallback so an initialization failure renders a retry surface instead of nothing. `@elevasis/ui/auth` exports the headless base guard, whose `fallback` prop defaults to `null` -- a route that imports it and passes no `fallback` shows a blank screen for the entire initialization window and silently swallows blocking errors. Import the headless one only when the surface deliberately renders its own loading state.
67
+ `@elevasis/ui` publishes two components under this name and they behave differently. The one route files want is `@elevasis/ui/features/auth`: it wraps the base guard with an animated full-screen loader that fades out when initialization completes, and supplies a default `ServiceUnavailableScreen` fallback, which classifies the initialization failure and retries on a backoff instead of rendering nothing. `@elevasis/ui/auth` exports the headless base guard, whose `fallback` prop defaults to `null` -- a route that imports it and passes no `fallback` shows a blank screen for the entire initialization window and silently swallows blocking errors. Import the headless one only when the surface deliberately renders its own loading state.
66
68
 
67
69
  ```tsx
68
70
  import { ProtectedRoute } from '@elevasis/ui/features/auth'
@@ -245,7 +247,7 @@ Section guards currently follow this pattern:
245
247
  - `AccessGuard` on sections that should hard-stop when the backing System is disabled or the role lacks the permission -- `/crm` uses `accessKey="sales.crm"`, `/lead-gen` uses `"sales.lead-gen"`, `/projects` uses `"platform.projects"`, and `/operations` and `/monitoring` both use `AccessKeys.operationsRead`
246
248
  - provider-level shell gating for shared System nav and sub-shell behavior
247
249
 
248
- The app shell in `__root.tsx` derives visible nav from `shellModel.systems` and `getSidebarLinks()`, filters admin-only entries locally using the signed-in profile, and passes `canonicalOrganizationModel` into `ElevasisSystemsProvider` so shared nav labels, paths, and graph runtime behavior resolve from the same organization-model semantic source.
250
+ `__root.tsx` filters `SYSTEM_MANIFESTS` (imported from `@/config/shell`) through `selectDeclaredSystems` against `canonicalOrganizationModel`, so a System module only mounts when the organization model declares it, and passes `canonicalOrganizationModel` into `ElevasisSystemsProvider` so shared nav labels, paths, and graph runtime behavior resolve from the same semantic source. Visible nav is derived inside the shared shell from the declared manifests and the signed-in profile -- the template does not compute it.
249
251
 
250
252
  ## Dashboard and Feature Areas
251
253
 
@@ -280,8 +282,8 @@ The main template-owned customization surfaces are:
280
282
  - `ui/src/config/theme.ts` -- theme presets and defaults
281
283
  - `ui/src/config/background.tsx` -- shared background treatment
282
284
  - `ui/src/config/loader.tsx` -- global loader element
283
- - `ui/src/config/nav-items.ts` -- app-local nav entries, including the preserved dashboard/home entry
284
- - `core/config/organization-model.ts` -- product labels, System availability, resource descriptors, semantic surfaces, canonical-to-legacy surface aliases, and quick-access behavior
285
+ - `ui/src/config/shell.tsx` -- the `SYSTEM_MANIFESTS` array of published System modules the shell mounts
286
+ - `core/config/organization-model/` -- product labels, System availability, resource descriptors, semantic surfaces, canonical-to-legacy surface aliases, and quick-access behavior
285
287
  - `ui/src/config/README.md` -- the deeper guide for those config files
286
288
 
287
289
  ## Customizing a Shared Full-Height Page (never wrap it)
@@ -296,11 +298,11 @@ Wrapping one of these pages in a layout element to bolt on extra UI breaks the h
296
298
 
297
299
  ## Customizing System Sidebars
298
300
 
299
- The template demonstrates one override pattern in `ui/src/routes/__root.tsx`: it extends `CRM_ITEMS` with a template-owned Reports link and replaces `crmManifest` with `customCrmManifest` in the System module array. The backing route lives at `ui/src/routes/crm/reports.tsx` -- delete both the nav item and the route if you don't need them.
301
+ `ui/src/config/shell.tsx` is where sidebar overrides go. The template ships `crmManifest` unmodified in `SYSTEM_MANIFESTS`, so there is no worked override in the template source to copy -- `shell.tsx`'s own doc comment points at `node_modules/@elevasis/sdk/reference/scaffold/ui/customization.md` for the decision tree and patterns. A template-owned `ui/src/routes/crm/reports.tsx` route exists but is not currently surfaced by any nav override; wire it through the `items` prop or a manifest spread below, or delete it.
300
302
 
301
303
  Two customization layers are available for every shared System sidebar:
302
304
 
303
- 1. **Nav-item shortcut (`items` prop)** -- when you just need to swap or extend the nav array, spread the published items constant and pass the result to `*SidebarMiddle`. The template's CRM customization uses this path.
305
+ 1. **Nav-item shortcut (`items` prop)** -- when you just need to swap or extend the nav array, spread the published items constant and pass the result to `*SidebarMiddle`. The template does not use this path today -- it is the shape the unwired `crm/reports.tsx` route would need.
304
306
 
305
307
  ```tsx
306
308
  import { crmManifest, CrmSidebar, CrmSidebarMiddle, CRM_ITEMS } from '@elevasis/ui/features/crm'
@@ -39,7 +39,9 @@ The user wants to record something new -- a task, a note, a piece of information
39
39
 
40
40
  The user wants to know something about current state -- task priorities, what is pending, what is running, what failed.
41
41
 
42
- **Recognize by:** questions about the current list, status, or queue of things. Includes both static-model queries ("what systems are on?") and runtime-entity queries ("what's pending in the queue?"). Route Query to static-model sources (org model, Systems/Actions config) or runtime sources (operations domain) based on the referenced entity.
42
+ **Recognize by:** questions about the current list, status, or queue of things. Includes static-model queries ("which systems are wired up?"), runtime-entity queries ("what's pending in the queue?"), and readiness diagnostics ("is content ready?"). Route Query to static-model sources (org model, Systems/Actions config), runtime sources (operations domain), or the readiness diagnostic (`om:doctor`, `/operations/systems`) based on the referenced entity.
43
+
44
+ **A System being "on" is three fields, not one.** Answering from `systems.ts`'s `enabled` alone gives the wrong answer, and the shipped template proves it: lead-gen, CRM, and `content` all read `enabled: true` while all three carry `lifecycle: 'draft'`, so none is actually reachable. `lifecycle` is the adoption switch — `draft` / `beta` / `active` / `deprecated` / `archived`, defaulting to active when absent. `enabled: false` is a deprecated short-circuit that still wins ahead of the lifecycle check, so read it too. And a System with API-backed actions needs an `apiInterface` before those actions work at all. When the question is whether a System is genuinely usable, run `om:doctor` or point the user at `/operations/systems` rather than narrating a field.
43
45
 
44
46
  **Fixture examples:**
45
47
 
@@ -48,10 +50,21 @@ The user wants to know something about current state -- task priorities, what is
48
50
  | "What should I work on next?" | Asking for prioritized task list |
49
51
  | "What's pending in the HITL queue?" | Runtime-entity query about operations state |
50
52
  | "What runs this week?" | Runtime query about upcoming schedules |
51
- | "What systems are enabled for this project?" | Static-model query about Systems config |
53
+ | "Which systems are actually wired up?" | Static-model query about System adoption |
52
54
  | "What's waiting on review?" | Runtime-entity query about content review-gate state |
55
+ | "Where did this go out?" | Distribution-row query -- which targets were written |
56
+ | "What platforms did this ship to?" | Distribution-row query, phrased by destination |
57
+ | "What are the steps in this pipeline?" | Pipeline step-contract query |
58
+ | "What review gates does this pipeline have?" | Pipeline step-contract query, phrased by gate |
59
+ | "Is the content system ready?" | Readiness diagnosis, not a config read |
60
+
61
+ **Agent action:** read the relevant source and narrate the answer in plain language. Use org model or `project:*` for project state, `elevasis-sdk queue:list --status pending --pretty` and `queue:status --pretty` for HITL queue state, `elevasis-sdk schedule:list --status active --pretty` for upcoming recurring automation, and for the content platform: `elevasis-sdk content:queue --pretty` (open review gates), `content:list --pretty` (item overview), `content:board <pipelineId> --pretty` (pipeline state), `content:get <itemId> --pretty` (one item's attempt/distribution history), `content:distributions --pretty` (distribution rows, one per platform/format target per item), `content:pipeline [id] --pretty` (pipeline templates, or one pipeline's step contract), `content:source-assets --pretty` (list available source assets), or `content:source-asset <id> --pretty` (one source asset's detail). No writes.
62
+
63
+ **`content:distributions` vs `content:get`.** They overlap and the distinction is worth keeping straight: `content:get <itemId>` answers _what happened to this item_; `content:distributions` answers _what went out across the pipeline_ — a filterable cross-item read the per-item command cannot do. It takes `--content-item-id`, `--pipeline-id`, `--platform`, `--status`, `--limit`, and `--offset`.
64
+
65
+ **`content:pipeline` reads the deployed snapshot, not your local model.** With no id it lists pipeline templates; with an id it returns that pipeline's step contract (step keys and review modes). It reads the **deployed** Organization Model snapshot, so a model edit that was never redeployed returns a stale-snapshot **503**. That is a missing redeploy, not a platform failure — do not report it as a bug. `content:pipeline <id>` is also the most direct way to resolve a step key before `content:review --step`; the Transition intent says never to guess `--step`, and this is the second route to getting it right (`content:queue` is the other).
53
66
 
54
- **Agent action:** read the relevant source and narrate the answer in plain language. Use org model or `project:*` for project state, `elevasis-sdk queue:list --status pending --pretty` and `queue:status --pretty` for HITL queue state, `elevasis-sdk schedule:list --status active --pretty` for upcoming recurring automation, and for the content platform: `elevasis-sdk content:queue --pretty` (open review gates), `content:list --pretty` (item overview), `content:board <pipelineId> --pretty` (pipeline state), `content:get <itemId> --pretty` (one item's attempt/distribution history), `content:source-assets --pretty` (list available source assets), or `content:source-asset <id> --pretty` (one source asset's detail). No writes.
67
+ **Readiness is a diagnostic read, and it belongs here.** `elevasis-sdk om:doctor` is the answer to "is this System actually usable" it reports per-issue readiness diagnostics against the deployed snapshot, where plain `doctor` prints only the compact roster. Reach for it whenever a question is about whether something is _wired up_ rather than what the model _says_. A tenant can also read the same picture in the UI at `/operations/systems`, which every project mounts.
55
68
 
56
69
  ### 3. Describe
57
70
 
@@ -97,7 +110,7 @@ The user wants to change the status of a task or entity.
97
110
  | "Pause the Friday report" | Changes schedule state |
98
111
  | "Approve this post" | Clears a named content review gate |
99
112
 
100
- **Agent action:** identify the task, queue item, schedule, or entity being transitioned, confirm the new status/action with the user, then apply it via `elevasis-sdk project:task:save`, `elevasis-sdk queue:select <id> --action-id <id>`, `elevasis-sdk queue:expire <id>`, `elevasis-sdk schedule:pause <id>`, `schedule:resume <id>`, `schedule:cancel <id>`, or `elevasis-sdk content:review <itemId> --step <key> (--approve | --reject) --user <email>` as appropriate. `content:review` always requires resolving the exact `stepKey` from `content:queue` first and confirming the item/decision before executing -- never guess `--step`. Never auto-transition without confirmation if the target entity is ambiguous.
113
+ **Agent action:** identify the task, queue item, schedule, or entity being transitioned, confirm the new status/action with the user, then apply it via `elevasis-sdk project:task:save`, `elevasis-sdk queue:select <id> --action-id <id>`, `elevasis-sdk queue:expire <id>`, `elevasis-sdk schedule:pause <id>`, `schedule:resume <id>`, `schedule:cancel <id>`, or `elevasis-sdk content:review <itemId> --step <key> (--approve | --reject) --user <email>` as appropriate. `content:review` always requires resolving the exact `stepKey` first and confirming the item/decision before executing -- never guess `--step`. Two commands resolve it: `content:queue` (what is currently open) and `content:pipeline <id>` (the pipeline's declared step contract, which is the more direct route when you know the pipeline). Never auto-transition without confirmation if the target entity is ambiguous.
101
114
 
102
115
  ### 5. Navigate
103
116
 
@@ -112,6 +125,7 @@ The user wants to shift focus -- to a different task, project, System, Action, o
112
125
  | "Let's focus on the onboarding flow for now" | "Focus on" + scope target |
113
126
  | "Switch to the Shopify integration project" | "Switch to" = navigate to a different project scope |
114
127
  | "Back to the CRM tasks" | "Back to" = return to a prior scope |
128
+ | "Open the systems page" | "Open" + a real route -- `/operations/systems` |
115
129
 
116
130
  **Agent action:** update the active scope in `prj_tasks.resume_context` (current project + task pointer), then narrate the new scope in plain language so the user knows where they are.
117
131
 
@@ -158,7 +172,9 @@ For "build/extend lead gen" / "campaign creator" / "outbound list state" asks, c
158
172
 
159
173
  For "add a custom CRM action" / "Send Quote button" asks, classify as Codify, then read `operations/node_modules/@elevasis/sdk/reference/scaffold/recipes/customize-crm-actions.md` before editing. Start with the shared `crmActions` provider path for action visibility, labels, ordering, and render-time configuration. In v1, platform-known/default action endpoint behavior is server-constrained; use project-owned UI that calls the workflow directly when a custom key sits outside that server-dispatched set.
160
174
 
161
- For "build/extend content" / "add a content pipeline" / "content review screen" asks, classify the structural org-model portion as Codify, then read `operations/node_modules/@elevasis/sdk/reference/scaffold/recipes/extend-content.md` before editing. A content pipeline is a catalog record, not code -- content work often spans org-model pipeline/step/status/pillar catalogs, the `content` workflow adapter, shared review-page composition, and distribution tracking; do not reduce it to only catalog config or only UI.
175
+ For "build/extend content" / "add a content pipeline" / "content review screen" asks, classify the structural org-model portion as Codify, then read `operations/node_modules/@elevasis/sdk/reference/scaffold/recipes/extend-content.md` before editing. A content pipeline is a catalog record, not code -- content work often spans org-model pipeline/step/status/pillar catalogs, the `content` workflow adapter, the tenant's own Workspace screens, and distribution tracking; do not reduce it to only catalog config or only UI.
176
+
177
+ **Know which side of the line the ask falls on before routing it.** `extend-content.md`'s "Own the Workspace" section draws it: **you own the surfaces where content is operated on; the platform owns the surfaces where it is observed.** Creating a piece and clearing its review gates are yours — authored as the tenant's own Workspace screens, reached through `workspaceRoute` at the pipeline and step level and composed with `ContentWorkspaceShell`. The board, items list, attempt history, distributions, and cross-pipeline review queue are shared, display-only, and do not write. So "build a content review screen" is a **tenant Workspace** build, not a customization of shared review-page composition -- building it on the platform side puts it on the wrong side of a line the recipe now forbids.
162
178
 
163
179
  Heuristics for when to propose codification (passed to `/om` as context):
164
180
 
@@ -183,6 +199,11 @@ The user wants to enable or disable a System.
183
199
 
184
200
  **Agent action:** delegate to `/om systems`. The ceremony (confirm + edit `core/config/organization-model/systems.ts` -- or `core/config/organization-model.ts` in unsplit projects -- + typecheck) belongs to `/om`, not to the ambient rule.
185
201
 
202
+ **Check adoption before flipping anything — `lifecycle` is the switch, not `enabled`.** Run `om:doctor` (or read `/operations/systems`) first and pass what it says to `/om` as context. Two things go wrong without that check:
203
+
204
+ - **"Turn on X" actioned against `enabled`** does nothing when `enabled` is already `true`, which it is for every template System. The field the user means is `lifecycle`.
205
+ - **Flipping `lifecycle: 'draft'` to `'active'` on an unadopted System produces guaranteed 503s.** A System with no scoped resources and no `apiInterface` computes as ready and then fails every request. "On" means adopted — resources, ontology, and an `apiInterface` where API-backed actions are involved — not one edited field. If the readiness check says the System is not adopted, say so in plain language instead of making it look enabled.
206
+
186
207
  **Tenant-local only.** Toggle operates on this project's own `core/config/organization-model/systems.ts` (or `core/config/organization-model.ts` in unsplit projects) — the project's own Systems. The Elevasis platform's own Systems are not in scope; this project cannot toggle them and vibe must not pretend it can. If a user names a platform-only System, surface the boundary in plain language rather than attempting a toggle.
187
208
 
188
209
  ### 8. Operate
@@ -271,4 +292,5 @@ Layers 2 (Public API), 3 (UI Shell Runtime), 5 (Toolkit), and 6 (Graph) require
271
292
  - `operations/node_modules/@elevasis/sdk/reference/scaffold/recipes/extend-lead-gen.md` -- lead-gen build/extend scope
272
293
  - `operations/node_modules/@elevasis/sdk/reference/scaffold/recipes/customize-crm-actions.md` -- custom CRM action keys and the `crmActions` provider
273
294
  - `operations/node_modules/@elevasis/sdk/reference/scaffold/recipes/extend-content.md` -- content build/extend scope
274
- - `core/config/organization-model/systems.ts` -- label vocabulary and System availability (or `core/config/organization-model.ts` in unsplit projects)
295
+ - `core/config/organization-model/systems.ts` -- label vocabulary, and the `lifecycle` / `enabled` / `apiInterface` fields a System's availability is read from (or `core/config/organization-model.ts` in unsplit projects). Read `elevasis-sdk om:doctor` rather than this file when the question is whether a System is genuinely adopted
296
+ - `/operations/systems` -- the readiness page every project mounts, showing which Systems are actually wired up
@@ -21,7 +21,7 @@ Vibe is **ambient and always on**. Every natural-language message is silently cl
21
21
  | Transition | "done", "stuck", "blocked", "finished", "complete", "approve", "pause" | Agent -- confirm + `project:task:save`, `queue:select`, `schedule:*`, or `content:review` |
22
22
  | Navigate | "focus on", "switch to", "back to", "look at" | Agent -- update scope + narrate |
23
23
  | Codify | "we are X", "we track Y", repeated attribute, "add type/field" | Delegate to `/om \<domain>` |
24
- | Toggle | "enable", "disable", "turn on/off" + system | Delegate to `/om systems` (tenant-local only) |
24
+ | Toggle | "enable", "disable", "turn on/off" + system | Check adoption (`om:doctor`), then delegate to `/om systems` (tenant-local only) |
25
25
  | Operate | "run", "execute", "launch", "trigger", "kick off", "start" + deployed resource | Delegate to `/elevasis` -- `elevasis-sdk describe` + confirm + `elevasis-sdk exec` |
26
26
 
27
27
  ## Safety Boundaries
@@ -31,6 +31,7 @@ Vibe is **ambient and always on**. Every natural-language message is silently cl
31
31
  - **Never write the model yourself.** Codify and Toggle detect intent and delegate to `/om <domain>`; the draft-confirm-write-typecheck ceremony belongs to `/om`.
32
32
  - **Never flip public agent exposure.** Making a deployed agent reachable on the public internet (the `agent_access_grants` row behind `/public/agents/:slug`, managed via `grant:create` / `grant:update` / `grant:disable` or the Resource-page public/private toggle) is a security boundary deliberately kept out of ambient routing -- a non-technical user describing their business must never expose an agent by accident. Do NOT classify "make my agent public" / "put the interview online" as Toggle, and do NOT auto-execute. Surface the `grant:*` CLI or the Resource-page toggle in plain language and require explicit confirmation.
33
33
  - **Toggle is tenant-local.** It edits this project's own Systems config -- `core/config/organization-model/systems.ts`, or `core/config/organization-model.ts` in unsplit projects -- only. The Elevasis platform's own Systems cannot be toggled from here; surface that boundary rather than attempting it.
34
+ - **"On" is `lifecycle`, and it is not the whole story.** `enabled` is already `true` on every template System, so actioning "turn on X" against it does nothing; `lifecycle` (`draft` / `beta` / `active` / ...) is the switch. But flipping `lifecycle` to `'active'` on a System with no scoped resources and no `apiInterface` makes it compute as ready and then 503 every request. Run `om:doctor` (or read `/operations/systems`) before delegating, and pass the result to `/om` as context. If the System is not adopted, say so rather than making it look enabled.
34
35
  - **Never guess an ambiguous intent.** Ask one neutral clarifying question presenting the plausible intents. Do not apply a precedence rule and do not route to the "closest" match.
35
36
  - **Never invent vocabulary.** Status, entity, and layer names come from the model's inline `label` fields, read verbatim -- never hardcoded synonyms.
36
37
 
@@ -64,4 +65,4 @@ a required question on every System-shaping change.
64
65
  ## References
65
66
 
66
67
  - `operations/node_modules/@elevasis/sdk/reference/rules/vibe-intents.md` -- per-intent recognition signals, fixture examples, exact agent actions, stage/state sub-routing, the classifier threshold, and phase scope
67
- - `core/config/organization-model/systems.ts` -- label vocabulary and System availability (or `core/config/organization-model.ts` in unsplit projects)
68
+ - `core/config/organization-model/systems.ts` -- label vocabulary, and the `lifecycle` / `enabled` / `apiInterface` fields a System's availability is read from (or `core/config/organization-model.ts` in unsplit projects). Run `elevasis-sdk om:doctor`, or read `/operations/systems`, when the question is whether a System is genuinely adopted rather than what the file says
@@ -101,25 +101,38 @@ The external skill doc (`.claude/skills/external/SKILL.md`) remains the workflow
101
101
 
102
102
  ### Per-Project Checks (auto-discovered)
103
103
 
104
- | Category | What It Checks |
105
- | -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
106
- | `deps` | `@elevasis/ui`, `@elevasis/sdk`, `@elevasis/core` versions match template |
107
- | `tier1` | registry-backed replace surfaces match template where verification still models them as exact baselines |
108
- | `org-os` | Organization model exists, exports canonical symbols, imports from `@elevasis/core/organization-model`, either calls `createFoundationOrganizationModel` or uses explicit `resolveOrganizationModel(..., { mergeDefaults: false })` canonical assembly, app-config references org model, `__root.tsx` uses `ElevasisAuthenticatedShell` / `ElevasisSystemsProvider` + `canonicalOrganizationModel`, `main.tsx` uses `createElevasisApp`, all 3 CSS subpath imports present |
109
- | `placeholders` | No unresolved `__PROJECT_SLUG__`, `__PROJECT_NAME__`, `__PROJECT_DESCRIPTION__` in key config files |
110
- | `scripts` | `ui` and `operations` `package.json` have required npm scripts |
111
- | `lib` | `ui/src/lib/`, `lib/`, `test-utils/` exist with minimum file counts |
112
- | `tier3` | project-owned preservation boundaries such as `nav-items.ts` remain intact |
113
- | `conflicts` | No merge conflict markers in source files |
114
- | `git` | Working tree is clean |
115
- | `lockfile` | `pnpm-lock.yaml` and `node_modules` exist |
104
+ | Category | What It Checks |
105
+ | ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
106
+ | `deps` | `@elevasis/ui`, `@elevasis/sdk`, `@elevasis/core` versions match template |
107
+ | `registry-owned` | registry-backed `replace` surfaces match the template byte-for-byte where verification still models them as exact baselines |
108
+ | `org-os` | Organization model exists, exports canonical symbols, imports from `@elevasis/core/organization-model`, either calls `createFoundationOrganizationModel` or uses explicit `resolveOrganizationModel(..., { mergeDefaults: false })` canonical assembly, app-config references org model, `__root.tsx` uses `ElevasisAuthenticatedShell` / `ElevasisSystemsProvider` + `canonicalOrganizationModel`, `main.tsx` uses `createElevasisApp`, all 3 CSS subpath imports present |
109
+ | `placeholders` | No unresolved `__PROJECT_SLUG__`, `__PROJECT_NAME__`, `__PROJECT_DESCRIPTION__` in key config files |
110
+ | `scripts` | `ui` and `operations` `package.json` have required npm scripts |
111
+ | `shared` | Five shared directories exist with minimum file counts: `ui/src/lib` (3), `ui/src/test-utils` (2), `ui/src/config` (3), `ui/src/routes` (5), `ui/src/features` (5) |
112
+ | `sync-plan` | Registry planner drift -- what the planner would write, and whether project-owned paths stay outside its scope |
113
+ | `project-marker` | The project's `.elevasis` marker file is present and well-formed |
114
+ | `claude` | `.claude/` infrastructure -- skills, rules, hooks, and settings the tenant agent loads |
115
+ | `knowledge` | Knowledge scaffold wiring and its generated nodes |
116
+ | `resource-governance` | Resource governance wiring resolves against the project's own OM |
117
+ | `contract-conformance` | Operations-bundle conformance via `detect-conformance-gaps.ts`; passes silently when the bundle is absent |
118
+ | `retired-nav` | Retired nav-item surfaces have not come back |
119
+ | `conflicts` | No merge conflict markers in source files |
120
+ | `git` | Working tree is clean |
121
+ | `lockfile` | `pnpm-lock.yaml` and `node_modules` exist |
122
+
123
+ An org-specific project (one not derived from the template) reports a single `mode` pass and skips the
124
+ dependency and `registry-owned` UI checks entirely.
116
125
 
117
126
  ### Monorepo-Level Checks
118
127
 
119
- | Category | What It Checks |
120
- | ----------- | ----------------------------------------------- |
121
- | `scaffold` | `pnpm scaffold:sync` passes (artifacts current) |
122
- | `artifacts` | 5 generated artifacts exist and have content |
128
+ | Category | What It Checks |
129
+ | ---------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
130
+ | `monorepo/artifacts` | Four hardcoded generated artifacts plus every registry-derived `generated` dependent exist and are non-empty |
131
+ | `monorepo/tombstones` | Tombstone lifecycle. A discharged tombstone warns rather than fails -- stale housekeeping should not turn the gate red |
132
+ | `monorepo/template-coverage` | Every file under `external/_template` is claimed by some registry entry |
133
+ | `monorepo/stale-prose-paths` | Prose in managed surfaces does not cite paths that no longer exist |
134
+ | `monorepo/external-registry` | The external registry's own status |
135
+ | `registry-owned/contract` | Every byte-equality-checked path is `replace`-category. Runs once per invocation, not per project |
123
136
 
124
137
  ### Usage
125
138
 
@@ -138,13 +151,14 @@ pnpm sync:verify -- nirvana-marketing # Single project
138
151
 
139
152
  ### Planner Interpretation Note
140
153
 
141
- `pnpm sync:verify` still carries some historical `tier1` / `tier3` labels in its output, but the ownership semantics now come from the registry-backed categories used by the planner:
154
+ `pnpm sync:verify` no longer emits `tier1` / `tier3` labels at all — the categories in the tables above are the real ones, and the ownership semantics come from the registry-backed categories used by the planner:
142
155
 
143
156
  - `.claude/skills/**`, `hooks/**`, `rules/**`, `scripts/**`, and `.claude/settings.json` are managed `replace` surfaces
144
- - `nav-items.ts`, `operations/src/**`, `shared/src/**`, `CLAUDE.md`, and extension files are `never-touch`
157
+ - `.elevasis`, `core/config/organization-model/profile.ts`, `operations/src/**`, `shared/src/**`, `CLAUDE.md`, and extension files are `never-touch`
158
+ - `ui/src/config/shell.tsx` is the project-owned shell seam. It carries no `never-touch` rule -- it is annotated `critical-manual-merge`, which is advisory, so edit it freely and expect a catch-up warning rather than a revert. `ui/src/routes/__root.tsx` and `ui/src/main.tsx` are the sync-managed files it was extracted from; put per-project shell customization in `shell.tsx`, not in those two
145
159
  - generated surfaces are verified for freshness, not copied
146
160
 
147
- Treat the old tier labels as display shorthand inside the verifier, not as the canonical execution contract.
161
+ The old `Tier 1` / `Tier 2` / `Tier 3` vocabulary survives only in this doc's history and in operator memory. Do not expect it in verifier output, and do not reintroduce it as shorthand — the registry categories are the canonical execution contract.
148
162
 
149
163
  ---
150
164
 
@@ -119,7 +119,7 @@ The output lands in `packages/sdk/reference/` which is included in the npm packa
119
119
  - UI patterns: `packages/ui/src/scaffold/...`
120
120
  - Cross-package or SDK-owned: `packages/sdk/docs/scaffold/...`
121
121
 
122
- 2. **Add to `SCAFFOLD_COPIES`** in `packages/sdk/scripts/copy-reference-docs.mjs`:
122
+ 2. **Add to the base `SCAFFOLD_COPIES` list** in `scripts/monorepo/scaffold-copies.js`. Do **not** edit the `SCAFFOLD_COPIES` constant in `packages/sdk/scripts/copy-reference-docs.mjs` -- that one is composed (`[...BASE_SCAFFOLD_COPIES, ...RULE_REFERENCE_COPIES]`) from two imported lists, so an entry added there is lost the next time either source list is edited:
123
123
 
124
124
  ```javascript
125
125
  { source: 'packages/sdk/docs/scaffold/operations/my-doc.md', target: 'scaffold/operations/my-doc.md' }
@@ -12,7 +12,7 @@ CRM deal pages derive their action buttons from an `ActionDef[]` array. The shar
12
12
 
13
13
  For the broader CRM extension map -- pages, sidebars, hooks, workflow adapters, System Interfaces, and org-model boundaries -- start with [Build and Extend CRM](extend-crm.md). This recipe is only the deal-action path.
14
14
 
15
- **Shape reference:** The `ActionDef` flat shape and the consolidation that replaced the old `handler`/`kind` union are documented in `apps/docs/content/docs/in-progress/active-development/_pipeline/crm/crm-current-state-assessment.mdx`.
15
+ **Shape reference:** The `ActionDef` flat shape and the consolidation that replaced the old `handler`/`kind` union are documented in `apps/docs/content/docs/technical/features/operations/crm/index.mdx`.
16
16
 
17
17
  Use this recipe when a user asks for work like:
18
18
 
@@ -348,8 +348,8 @@ Everything you need to build a combined create-and-review screen is already expo
348
348
  `resolveContentStepResource`.
349
349
 
350
350
  **`Workspace` is a fixed platform label and is not vocabulary-driven.** Rename the page, not the
351
- section. (The section was called `Create` before 2026-08-15. `createItems` still works as a
352
- deprecated alias for one minor; move to `workspaceItems`.)
351
+ section. (The section was called `Create` before 2026-08-15. The `createItems` prop was renamed to
352
+ `workspaceItems`; the deprecated alias has since been removed, so `workspaceItems` is the only name.)
353
353
 
354
354
  ### Point the shared pages at your Workspace
355
355
 
@@ -67,7 +67,7 @@ Deal list and detail responses include a server-derived `priority` object. Use `
67
67
  For a simple nav addition, extend `CRM_ITEMS` and override the CRM system manifest:
68
68
 
69
69
  ```tsx
70
- // ui/src/routes/__root.tsx
70
+ // ui/src/config/shell.tsx
71
71
  import { crmManifest, CRM_ITEMS, CrmSidebar, CrmSidebarMiddle } from '@elevasis/ui/features/crm'
72
72
  import type { SystemModule } from '@elevasis/ui/provider'
73
73
  import type { NavItem } from '@elevasis/ui/layout'
@@ -90,7 +90,9 @@ export const customCrmManifest: SystemModule = {
90
90
  }
91
91
  ```
92
92
 
93
- Then replace `crmManifest` with `customCrmManifest` in the local `SYSTEM_MANIFESTS` array and add the matching route under `ui/src/routes/crm/`.
93
+ Then replace `crmManifest` with `customCrmManifest` in the `SYSTEM_MANIFESTS` array — which is declared in this same file, `ui/src/config/shell.tsx` — and add the matching route under `ui/src/routes/crm/`.
94
+
95
+ **Do this in `shell.tsx`, not in `ui/src/routes/__root.tsx`.** `__root.tsx` imports `SYSTEM_MANIFESTS` from `@/config/shell` and filters it through `selectDeclaredSystems`; it does not declare the array. It is also `replace`-category with a `replace-all` strategy, so anything you write into it is overwritten outright on the next sync -- not preserved, not merged, not flagged. `shell.tsx` is the seam that exists for exactly this edit.
94
96
 
95
97
  For structural changes, compose `CrmSidebarTop`, `SubshellNavList`, `SubshellSidebarSection`, `MyTasksPanel`, and `QuickCreateActions`. The full sidebar decision tree lives in `operations/node_modules/@elevasis/sdk/reference/scaffold/ui/customization.md`.
96
98
 
@@ -73,7 +73,7 @@ Look for the **Lead Gen Platform Primitives** section. It includes list shapes,
73
73
  For a simple nav addition, extend `LEAD_GEN_ITEMS` and override the lead-gen system manifest:
74
74
 
75
75
  ```tsx
76
- // ui/src/routes/__root.tsx
76
+ // ui/src/config/shell.tsx
77
77
  import { leadGenManifest, LEAD_GEN_ITEMS, LeadGenSidebar, LeadGenSidebarMiddle } from '@elevasis/ui/features/lead-gen'
78
78
  import type { SystemModule } from '@elevasis/ui/provider'
79
79
  import type { NavItem } from '@elevasis/ui/layout'
@@ -96,7 +96,9 @@ export const customLeadGenManifest: SystemModule = {
96
96
  }
97
97
  ```
98
98
 
99
- Then replace `leadGenManifest` with `customLeadGenManifest` in the local `SYSTEM_MANIFESTS` array and add the matching route under `ui/src/routes/lead-gen/`.
99
+ Then replace `leadGenManifest` with `customLeadGenManifest` in the `SYSTEM_MANIFESTS` array — which is declared in this same file, `ui/src/config/shell.tsx` — and add the matching route under `ui/src/routes/lead-gen/`.
100
+
101
+ **Do this in `shell.tsx`, not in `ui/src/routes/__root.tsx`.** `__root.tsx` imports `SYSTEM_MANIFESTS` from `@/config/shell` and filters it through `selectDeclaredSystems`; it does not declare the array. It is also `replace`-category with a `replace-all` strategy, so anything you write into it is overwritten outright on the next sync -- not preserved, not merged, not flagged. `shell.tsx` is the seam that exists for exactly this edit.
100
102
 
101
103
  For structural changes, compose `LeadGenSidebarTop`, `SubshellNavList`, and `SubshellSidebarSection`. The full sidebar decision tree lives in `operations/node_modules/@elevasis/sdk/reference/scaffold/ui/customization.md`.
102
104
 
@@ -185,14 +187,18 @@ const App = createElevasisApp({
185
187
  ```
186
188
 
187
189
  ```tsx
188
- // ui/src/routes/__root.tsx
190
+ // ui/src/routes/__root.tsx (sync-managed — shown for reference, not for editing)
189
191
  import { ElevasisAuthenticatedShell } from '@elevasis/ui/app'
190
192
  import { canonicalOrganizationModel } from '@core/config/organization-model'
193
+ import { selectDeclaredSystems } from '@elevasis/ui/provider'
194
+ import { SYSTEM_MANIFESTS } from '@/config/shell'
195
+
196
+ const DECLARED_SYSTEM_MANIFESTS = selectDeclaredSystems(SYSTEM_MANIFESTS, canonicalOrganizationModel)
191
197
 
192
- export function RootLayout() {
198
+ function RootLayoutComponent() {
193
199
  return (
194
200
  <ElevasisAuthenticatedShell
195
- systems={SYSTEM_MANIFESTS}
201
+ systems={DECLARED_SYSTEM_MANIFESTS}
196
202
  organizationModel={canonicalOrganizationModel}
197
203
  // other shell config...
198
204
  />
@@ -264,6 +270,7 @@ const listActions: ListBuilderRegistry = [
264
270
  }
265
271
  ]
266
272
 
273
+ // Declared in ui/src/config/shell.tsx and imported here.
267
274
  const SYSTEM_MANIFESTS = [
268
275
  leadGenManifest,
269
276
  // Add any other shared/local system manifests here.
@@ -493,6 +500,28 @@ export const organizationModel = {
493
500
  }
494
501
  ```
495
502
 
503
+ ### Which authoring shape to use
504
+
505
+ Two shapes exist and the readiness profile decides which one you get.
506
+
507
+ **Adopting one of the platform's catalogued profiles** — `SYSTEM_INTERFACE_PROFILES` holds four: `sales.lead-gen.api`, `sales.crm.api`, and `sales.lead-gen.crm-handoff`, which are the three built-in-validated ones, plus `content.api`, which is contract-validated. All four go through the helper — use the `defineSystemApiInterface` helper from `@elevasis/core/organization-model`, not a hand-written literal. It is what `pnpm elevasis-sdk om:emit-api-interface` generates, so the helper keeps your hand-authored System and your scaffolded one the same shape:
508
+
509
+ <!-- doc-snippet:skip: illustrative excerpt, not a standalone compilable file -->
510
+
511
+ ```ts
512
+ import { defineSystemApiInterface } from '@elevasis/core/organization-model'
513
+
514
+ export const prospectingApiInterface = defineSystemApiInterface({
515
+ systemPath: 'sales.lead-gen',
516
+ readinessProfile: 'sales.lead-gen.api',
517
+ resourceIds: ['acme-list-build-workflow', 'acme-list-export-workflow']
518
+ })
519
+ ```
520
+
521
+ The helper throws at declaration time on the failure modes the literal lets through silently — most importantly an **empty `resourceIds`**, which otherwise computes as ready and then 503s every request. That is why `resourceIds` is typed `[string, ...string[]]`. **"No scoped resources" means omit the `apiInterface` block entirely**, never hand-write one with an empty list.
522
+
523
+ **Declaring a custom `readinessProfile`**, as the `acme.prospecting.api` block above does, the helper is not available to you: it validates `readinessProfile` against `SYSTEM_INTERFACE_PROFILES` — a platform-owned catalog in `@elevasis/core` — and throws on anything outside it. A custom profile therefore stays a hand-written literal, and its `readinessContract` is mandatory for the reason given above. Do not add your id to `SYSTEM_INTERFACE_PROFILES`; that is platform source, not tenant source.
524
+
496
525
  `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.
497
526
 
498
527
  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.
@@ -28,10 +28,10 @@ There is one pattern for customizing a system's sidebar or pages: set `sidebar`
28
28
 
29
29
  Extend CRM's nav by spreading `CRM_ITEMS` and appending a Reports entry. Pass the extended array to `CrmSidebarMiddle`.
30
30
 
31
- Wire the override in `ui/src/routes/__root.tsx`, where system manifests are assembled before being passed to `ElevasisSystemsProvider`.
31
+ Wire the override in `ui/src/config/shell.tsx`, the customization seam that owns the manifest list. `ui/src/routes/__root.tsx` imports `SYSTEM_MANIFESTS` from there and filters it through `selectDeclaredSystems` before mounting, so a manifest whose System the org model does not declare is dropped automatically.
32
32
 
33
33
  ```tsx
34
- // ui/src/routes/__root.tsx (excerpt -- add after existing imports)
34
+ // ui/src/config/shell.tsx (excerpt -- add after existing imports)
35
35
 
36
36
  import { crmManifest, CrmSidebar, CrmSidebarMiddle, CRM_ITEMS } from '@elevasis/ui/features/crm'
37
37
  import { IconFileText } from '@tabler/icons-react'
@@ -58,13 +58,13 @@ Then swap `crmManifest` for `customCrmManifest` in the `SYSTEM_MANIFESTS` array:
58
58
 
59
59
  ```tsx
60
60
  // In the SYSTEM_MANIFESTS array (same file)
61
- const SYSTEM_MANIFESTS: SystemModule[] = [
61
+ export const SYSTEM_MANIFESTS: SystemModule[] = [
62
62
  leadGenManifest,
63
63
  customCrmManifest, // <-- replaces crmManifest
64
64
  deliveryManifest,
65
65
  operationsManifest,
66
- monitoringManifest,
67
- settingsManifest
66
+ submittedRequestsManifest,
67
+ knowledgeManifest
68
68
  ]
69
69
  ```
70
70
 
@@ -15,7 +15,7 @@ Create `ui/src/routes/my-system.index.tsx`:
15
15
  ```tsx
16
16
  import { createFileRoute } from '@tanstack/react-router'
17
17
  import { ProtectedRoute } from '@/features/auth'
18
- import { AppShellContentContainer, AppTopbarAdjusterWrapper, PageContainer } from '@elevasis/ui/layout'
18
+ import { AppPageLayout } from '@elevasis/ui/layout'
19
19
  import { MySystemPage } from '@/features/my-system/components/MySystemPage'
20
20
 
21
21
  export const Route = createFileRoute('/my-system/')({
@@ -25,18 +25,18 @@ export const Route = createFileRoute('/my-system/')({
25
25
  function MySystemRouteComponent() {
26
26
  return (
27
27
  <ProtectedRoute>
28
- <AppTopbarAdjusterWrapper>
29
- <AppShellContentContainer>
30
- <PageContainer>
31
- <MySystemPage />
32
- </PageContainer>
33
- </AppShellContentContainer>
34
- </AppTopbarAdjusterWrapper>
28
+ <AppPageLayout>
29
+ <MySystemPage />
30
+ </AppPageLayout>
35
31
  </ProtectedRoute>
36
32
  )
37
33
  }
38
34
  ```
39
35
 
36
+ `AppPageLayout` is the standard route shell — topbar offset, the shell's padded content region, then the page container. It replaced the hand-written three-component nesting of `AppTopbarAdjusterWrapper` / `AppShellContentContainer` / `PageContainer`; do not write that nesting yourself. Pass `contained={false}` for a page that manages its own width (the monitoring dashboards do, since their charts run full-bleed) — that prop is the escape hatch, not a re-nest.
37
+
38
+ For a page that sits inside a **subshell sidebar**, use `SubshellPageLayout` from the same subpath instead. The two are mutually exclusive: `AppPageLayout` pushes the whole main region down, while the subshell applies the topbar offset to the scrolling right side only so the sidebar starts at the top. Nesting one in the other double-pads the page and breaks the `height: 100%` chain, so the inner scroll never engages. A subshell page that must run full-bleed renders `SubshellContentContainer` directly — there is deliberately no `contained` prop on `SubshellPageLayout`.
39
+
40
40
  Add the sidebar entry as a system node in `core/config/organization-model.ts`:
41
41
 
42
42
  `systems` is an id-keyed record, not an array, and `order` is required on every entry:
@@ -68,13 +68,9 @@ function MySystemRouteComponent() {
68
68
  return (
69
69
  <ProtectedRoute>
70
70
  <AccessGuard accessKey="my-system">
71
- <AppTopbarAdjusterWrapper>
72
- <AppShellContentContainer>
73
- <PageContainer>
74
- <MySystemPage />
75
- </PageContainer>
76
- </AppShellContentContainer>
77
- </AppTopbarAdjusterWrapper>
71
+ <AppPageLayout>
72
+ <MySystemPage />
73
+ </AppPageLayout>
78
74
  </AccessGuard>
79
75
  </ProtectedRoute>
80
76
  )
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  title: "Integration Adapters"
3
- description: "Auto-generated table of all 13 integration (credential-bound) adapters exported from @elevasis/sdk/worker, derived from static analysis of the adapter source files."
3
+ description: "Auto-generated table of all 14 integration (credential-bound) adapters exported from @elevasis/sdk/worker. Each row's methods are read from that adapter's METHODS array; the adapter set is the INTEGRATION_EXPORTS list in the generator, checked against the adapter directory on every run."
4
4
  ---
5
5
 
6
6
  {/* @generated by .claude/_gen/sync-sdk-adapters.ts — DO NOT EDIT */}
@@ -16,6 +16,7 @@ Integration adapters use a factory pattern. Call `create*Adapter(credential)` wi
16
16
  | Dropbox | `createDropboxAdapter(credential)` | `uploadFile`, `createFolder`, `listFolder`, `getMetadata`, `getTemporaryLink`, `createSharedLink`, `download`, `getThumbnail`, `getThumbnailBatch` | Dropbox — upload files and create folders. |
17
17
  | Gmail | `createGmailAdapter(credential)` | `sendEmail` | Gmail — send emails via a bound Gmail credential. |
18
18
  | GoogleSheets | `createGoogleSheetsAdapter(credential)` | `readSheet`, `writeSheet`, `appendRows`, `clearRange`, `getSpreadsheetMetadata`, `batchUpdate`, `getHeaders`, `getLastRow`, `getRowByValue`, `updateRowByValue`, `upsertRow`, `filterRows`, `deleteRowByValue` | Google Sheets — read, write, append, filter, and manage spreadsheet data. |
19
+ | Instagram | `createInstagramAdapter(credential)` | `createMediaContainer`, `createCarouselContainer`, `publishContainer`, `getContainerStatus`, `getMediaPermalink`, `getPublishingLimit`, `getMediaInsights`, `refreshToken` | Instagram — Content Publishing container flow (create, poll, publish, permalink) plus media insights. |
19
20
  | Instantly | `createInstantlyAdapter(credential)` | `sendReply`, `removeFromSubsequence`, `getEmails`, `updateInterestStatus`, `addToCampaign`, `listCampaigns`, `getCampaign`, `updateCampaign`, `pauseCampaign`, `activateCampaign`, `getCampaignAnalytics`, `getStepAnalytics`, `bulkAddLeads`, `getAccountHealth`, `createInboxTest`, `createCampaign`, `getDailyCampaignAnalytics`, `listLeads`, `bulkDeleteLeads`, `deleteCampaign`, `patchLead` | Instantly — manage email outreach campaigns, leads, analytics, and inbox health. |
20
21
  | MillionVerifier | `createMillionVerifierAdapter(credential)` | `verifyEmail`, `checkCredits` | MillionVerifier — verify email deliverability and check account credits. |
21
22
  | Anymailfinder | `createAnymailfinderAdapter(credential)` | `findCompanyEmail`, `findPersonEmail`, `findDecisionMakerEmail`, `verifyEmail` | Anymailfinder — find and verify company and person email addresses. |
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  title: "Platform Adapters"
3
- description: "Auto-generated table of all 14 platform (singleton, no credential) adapters exported from @elevasis/sdk/worker, derived from static analysis of the adapter source files."
3
+ description: "Auto-generated table of all 14 platform (singleton, no credential) adapters exported from @elevasis/sdk/worker. Each row's methods are read from that adapter's METHODS array; the adapter set is the PLATFORM_EXPORTS list in the generator, checked against the adapter directory on every run."
4
4
  ---
5
5
 
6
6
  {/* @generated by .claude/_gen/sync-sdk-adapters.ts — DO NOT EDIT */}
@@ -1,9 +1,9 @@
1
1
  ---
2
2
  title: Platform Tools
3
- description: Access 25 adapters (13 integration + 12 platform) from your SDK workflows -- typed adapters, credential security model, and working code examples
3
+ description: Access 28 adapters (14 integration + 14 platform) from your SDK workflows -- typed adapters, credential security model, and working code examples
4
4
  ---
5
5
 
6
- Your SDK workflows have access to 25 adapters (13 integration + 12 platform) -- Gmail, Stripe, Google Sheets, PDF generation, human-in-the-loop approvals, storage, scheduling, and more. Credentials are managed server-side and never appear in your code.
6
+ Your SDK workflows have access to 28 adapters (14 integration + 14 platform) -- Gmail, Stripe, Google Sheets, PDF generation, human-in-the-loop approvals, storage, scheduling, and more. Credentials are managed server-side and never appear in your code.
7
7
 
8
8
  **Typed adapters** are the recommended way to call tools. They provide full TypeScript autocomplete, compile-time method checking, and eliminate boilerplate. Use `platform.call()` only for tools that don't have an adapter yet.
9
9
 
@@ -43,7 +43,7 @@ Both patterns return a Promise that resolves with the tool result or rejects wit
43
43
 
44
44
  ## Integration Adapters
45
45
 
46
- The SDK ships 13 integration adapters (credential-bound factory functions) covering third-party APIs -- Attio, Apify, ClickUp, Dropbox, Gmail, Google Sheets, Instantly, MillionVerifier, Anymailfinder, Tomba, Resend, SignatureAPI, and Stripe. Pass the credential name once at adapter creation. Supabase is covered separately under [Database Access](#database-access) below.
46
+ The SDK ships 14 integration adapters (credential-bound factory functions) covering third-party APIs -- Attio, Apify, ClickUp, Dropbox, Gmail, Google Sheets, Instagram, Instantly, MillionVerifier, Anymailfinder, Tomba, Resend, SignatureAPI, and Stripe. Pass the credential name once at adapter creation. Supabase is covered separately under [Database Access](#database-access) below.
47
47
 
48
48
  For the full per-adapter method tables, credential shapes, and code examples, see [Integration Adapters](adapters-integration.mdx) -- generated from the adapter source so it never drifts.
49
49
 
@@ -130,9 +130,9 @@ Credentials are created in the command center UI: navigate to Credentials -> Add
130
130
 
131
131
  ## Platform Services
132
132
 
133
- The SDK ships 12 platform service singletons available without a `credential` field -- imported directly from `@elevasis/sdk/worker`: `scheduler`, `llm`, `storage`, `notifications`, `acqDb`, `projects`, `crm`, `list`, `pdf`, `approval`, `execution`, and `email`. The platform injects context server-side, so no credential is passed.
133
+ The SDK ships 14 platform service singletons available without a `credential` field -- imported directly from `@elevasis/sdk/worker`: `scheduler`, `llm`, `storage`, `notifications`, `acqDb`, `projects`, `crm`, `list`, `pdf`, `approval`, `execution`, `email`, `artifacts`, and `content`. The platform injects context server-side, so no credential is passed.
134
134
 
135
- For the full per-service method tables (including the `acqDb` 56-method surface), see [Platform Adapters](adapters-platform.mdx) -- generated from the adapter source.
135
+ For the full per-service method tables (including the `acqDb` 60-method surface), see [Platform Adapters](adapters-platform.mdx) -- generated from the adapter source.
136
136
 
137
137
  ## LLM Tool
138
138
 
@@ -195,8 +195,8 @@ const qualified = await platform.call({
195
195
 
196
196
  ## Documentation
197
197
 
198
- - [Integration Adapters](adapters-integration.mdx) - All 13 integration adapters with method tables and code examples
199
- - [Platform Adapters](adapters-platform.mdx) - All 12 platform service adapters with method tables and code examples
198
+ - [Integration Adapters](adapters-integration.mdx) - All 14 integration adapters with method tables and code examples
199
+ - [Platform Adapters](adapters-platform.mdx) - All 14 platform service adapters with method tables and code examples
200
200
  - [Adapter Type Safety](type-safety.mdx) - Required fields, discriminated unions, and intentionally loose adapter types
201
201
 
202
202
  ---
@@ -325,14 +325,14 @@ const myAgent: AgentDefinition = {
325
325
 
326
326
  ### config (agent-specific fields)
327
327
 
328
- | Field | Type | Description |
329
- | ------------------- | ---------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
330
- | `kind` | `'orchestrator' | 'specialist' | 'utility' | 'platform'` | Required. What role this agent plays — not enforced at runtime today, but tenant-authored and validated at deploy against your OM resource descriptor's own `kind`. |
331
- | `systemPrompt` | `string` | Required. The agent's base system prompt. |
332
- | `constraints` | `{ maxIterations?, timeout?, maxSessionMemoryKeys?, maxMemoryTokens? }` (optional) | Iteration budget, execution timeout in ms, and session-memory limits. |
333
- | `sessionCapable` | `boolean` (optional) | Opt in to multi-turn sessions. Defaults to `false` — the shape used in the example above, which completes and returns `contract.outputSchema` in a single turn. |
334
- | `securityLevel` | `'standard' | 'hardened' | 'none'` (optional) | Prompt-hardening tier. Auto-derived from `sessionCapable` when omitted (`true` → `'hardened'`, `false` → `'standard'`). Never set `'none'` on a session-capable agent. |
335
- | `memoryPreferences` | `string` (optional) | Guidance injected into the system prompt when session memory management is enabled. |
328
+ | Field | Type | Description |
329
+ | ------------------- | ---------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
330
+ | `kind` | `'orchestrator' | 'specialist' | 'utility' | 'platform'` | Required. What role this agent plays — not enforced at runtime today, but tenant-authored and validated at deploy against your OM resource descriptor's own `kind`. |
331
+ | `systemPrompt` | `string` | Required. The agent's base system prompt. |
332
+ | `constraints` | `{ maxIterations?, timeout?, maxSessionMemoryKeys?, maxMemoryTokens? }` (optional) | Iteration budget, execution timeout in ms, and session-memory limits. |
333
+ | `sessionCapable` | `boolean` (optional) | Opt in to multi-turn sessions. Defaults to `false` — the shape used in the example above, which completes and returns `contract.outputSchema` in a single turn. |
334
+ | `securityLevel` | `'standard' | 'hardened' | 'none'` (optional) | Prompt-hardening tier. Auto-derived from `sessionCapable` when omitted (`true` → `'hardened'`, `false` → `'standard'`). `'none'` disables prompt hardening. It is a deliberate explicit opt-in, valid even for a session-capable agent when that agent's surface has no untrusted-input boundary to defend. |
335
+ | `memoryPreferences` | `string` (optional) | Guidance injected into the system prompt when session memory management is enabled. |
336
336
 
337
337
  ### contract (agent)
338
338