# CRM HITL Action Layer Cutover

## Why this note exists

The Elevasis platform completed an atomic cutover of `acq_deals` from the legacy `cached_stage` + `proposal_status` model to the canonical `pipeline_key` / `stage_key` / `state_key` runtime fields. The release train that ships this cutover bumps `@elevasis/core`, `@elevasis/ui`, and `@elevasis/sdk` together, and rewires server-side lifecycle to flow through a single `transitionItem()` boundary in the API.

Concretely, this train introduces:

- a flat `ActivityEventSchema` discriminated union on `@elevasis/core/business/acquisition` (replaces the older `DealActivityEntry` shape; activity rows now use fields like `stageBefore`, `stageAfter`, `stateBefore`, `stateAfter`, `taskId`, `timestamp`)
- a pure `deriveActions(deal)` helper on `@elevasis/core/business/acquisition` (UI/API action derivation from current deal state)
- new typed CRM hooks on `@elevasis/ui` (`useTransitionItem`, refreshed `useSyncDealStage`, kanban + deal-drawer wiring)
- canonical `transitionItem(dealId, organizationId, pipelineKey, stageKey, stateKey?, reason?, expectedUpdatedAt?)` as the single lifecycle mutation; server-side it appends activity, supersedes open command-queue rows, and closes incompatible tasks atomically
- a `command_queue`-backed HITL transport supersession lifecycle resolved by `resolveDealCommandQueue` (called inside `transitionItem`)

Derived projects that read or write `acq_deals` directly, render the CRM kanban from `@elevasis/ui`, or import acquisition types from `@elevasis/core` will pick up these contract changes when they upgrade through this train.

## Applies to

All template-derived projects that:

- consume `@elevasis/core/business/acquisition` types (`Deal`, `ActivityEvent`, action types) or import `deriveActions`
- render the CRM kanban / deal drawer surfaces from `@elevasis/ui`
- have a project-local `acq_deals` table that mirrors the platform schema
- author workflows under `operations/src/sales/crm/**` that call `acqDb.transitionItem(...)` (or formerly called `syncDealStage` / direct stage writes)
- read `activity_log` rows on deals and pattern-match on event shape

Operations-only projects with no CRM surface and no `acq_deals` table can ignore this train.

## Required actions

1. Pull template changes with `/git-sync` so the refreshed CRM hook + drawer/kanban consumers are available.

2. After the release train is published, update package versions in the project:

```bash
pnpm up @elevasis/core @elevasis/ui @elevasis/sdk --latest
```

3. If your project has a project-local `acq_deals` table, run a matching atomic cutover migration. The reference SQL that drove the monorepo cutover (preflight, outlier cleanup, atomic cutover, postflight) is no longer checked into the repo -- recover it from git history under `apps/docs/content/docs/in-progress/active-development/sdk-changes/org-model/_sql/` (deleted 2026-04-27 alongside the completed task doc) if you need a starting point.

   The cutover is single-shot, not dual-write. Backfill `pipeline_key` / `stage_key` from `cached_stage` and drop the legacy columns in the same transaction. There is no mid-flight rollback path; fix-forward only. Confirm prod deal volume is small enough to accept that tradeoff before applying.

4. Replace any direct stage writes with `acqDb.transitionItem(...)`:
   - workflow code that previously called `syncDealStage`, `updateDeal({ cached_stage: ... })`, or wrote `proposal_status` directly must now route through `transitionItem`
   - the API server injects `organizationId` from execution context — never pass `organizationId` from worker payload
   - the platform-side method appends `ActivityEventSchema`-shaped events; do NOT also write activity rows from the workflow side

5. If you read `activity_log` and pattern-match by `type`, update consumers to the flat schema:

```ts
// Stage change
{ type: 'stage_change', timestamp, stageBefore, stageAfter, reason? }

// State change
{ type: 'state_change', timestamp, stateBefore, stateAfter, reason? }

// Task created
{ type: 'task_created', timestamp, taskId }

// Approval resolved (written by resolveDealCommandQueue)
{ type: 'approval_resolved', timestamp, ... }
```

The legacy `{ type, title, payload, occurredAt }` envelope is gone.

6. If your project deploys SDK resources, redeploy `operations` after the package upgrades so workers pick up the new typed adapter surface and ToolMap entries:

```bash
pnpm -C operations exec elevasis-sdk deploy --prod
```

## Verification

Run from the project root after package updates and any DB migration:

```bash
pnpm -C ui check-types
pnpm -C ui build
pnpm -C operations check-types
pnpm -C operations exec elevasis-sdk check
```

Then exercise the CRM surface:

- open the deal kanban — drag a deal between stages; confirm the activity log shows a flat `stage_change` event with `stageBefore` / `stageAfter`
- open a deal drawer — confirm available actions render from `deriveActions(deal)` and not from a stored `available_actions` array
- if you have a HITL approval surface, confirm that completing or superseding an approval lands an `approval_resolved` activity event before any subsequent state change

If a workflow that historically wrote stage updates throws Zod errors against `ActivityEventSchema`, it is still on the legacy envelope — fix-forward by routing it through `acqDb.transitionItem(...)`.

## Not handled by /git-sync

- `/git-sync` does not publish or upgrade `@elevasis/core`, `@elevasis/ui`, or `@elevasis/sdk`.
- `/git-sync` does not run the project-local `acq_deals` cutover migration. The DB cutover is atomic and irreversible — only the project owner can authorize it.
- `/git-sync` does not redeploy the project's `operations` bundle to dev or prod.
- `/git-sync` does not rewrite project-owned workflow code that bypasses `transitionItem` to write stage / state directly.
- `/git-sync` does not reconcile project-owned `activity_log` consumers that still pattern-match on the legacy `DealActivityEntry` envelope.
