# Plan 002: Enforce delivery status preconditions on Convex mutations

> **Executor instructions**: Follow step by step. Verify each step. STOP on
> mismatch with current domain rules — do not invent transitions.
>
> **Drift check**:
> `git diff --stat 74051ba..HEAD -- packages/backend/convex/deliveries.ts packages/shared/src/domain.ts`

## Status

- **Priority**: P0
- **Effort**: M
- **Risk**: MED
- **Depends on**: ideally 001 (authz); can land assertStatus without 001 but incomplete
- **Category**: bug
- **Planned at**: commit `74051ba`, 2026-07-09

## Why this matters

UI gates actions via `deliveryActions` in `@dudata/shared`, but several Convex
mutations force `nextStatusFor({ action })` without checking `doc.status`.
Concurrent clients or direct API calls can jump e.g. `publicado` → `alocacao`
via `accept`, corrupt pipeline stamps, and fire wrong Slack events. Some
mutations already validate (`requestRework`, `approve`, `moveStage`) — make that
the universal pattern.

## Current state

- Shared machine: `packages/shared/src/domain.ts` — `deliveryActions` (~536+),
  `NEXT_STATUS` / `nextStatusFor` (~707+).
- Open transitions (examples): `accept` at `deliveries.ts:258-270` sets status
  with no precondition; `start`, `sendToReview`, `publish` (publish only checks
  pdf/link), `backToWork`, `reject`, `submitEdit`, `confirmNps` similarly weak.
- Validated: `requestRework`, `approve`, `moveStage` — **use these as exemplars**.
- `requireAnalyst` (~155) only checks slug exists — also fix: face/role/active.

## Commands

| Purpose | Command | Expected |
|---------|---------|----------|
| Shared tests | `cd packages/shared && bun run test` | pass |
| Backend typecheck | `cd packages/backend && bun run typecheck` | exit 0 |
| Monorepo test | `bun run test` | pass |

## Steps

### 1. Centralize allowed-from status map

In shared domain (preferred) or backend-only helper used by all mutations:

- For each action (`accept`, `allocate`, `start`, …) list **allowed current statuses**
  consistent with `deliveryActions` / CONTEXT.md lifecycle.
- Export `assertDeliveryAction({ delivery, action, actor, ... })` that throws a clear
  `ConvexError` in Portuguese when illegal.

Do **not** duplicate a second machine on mobile (see plan 004).

### 2. Wire every mutation

Before `patchDelivery`, call assert. Include:

- `submitEdit` only when `devolvido` (or product-allowed set)
- `confirmNps` only when status matches product rule (`aprovado` / published window — read mobile/web comments #89)
- `publish` keep pdf/link requirement **and** status precondition

### 3. Harden `requireAnalyst`

Require `face === "backoffice"`, `role === "analista"` (or documented allowlist),
and `effectiveStatus !== "inativo"`.

### 4. Tests

Add shared pure tests for illegal transitions; if backend harness exists (003),
integration tests for accept-on-publicado fails.

## Out of scope

- Pagination / list scoping (001 / PERF)
- Reworking Slack payload design
- UI copy changes beyond error toast wiring if errors already surface

## STOP conditions

- Ambiguity between CONTEXT.md table and newer statuses (`em_alteracao`, `aprovado`,
  `alocacao`) — re-read `domain.ts` + ADRs; if still unclear, stop and ask.
- BO “act as client” paths for `submitEdit` / `confirmNps` — must match issue #84/#89 comments in code, not invent god-mode.

## Done criteria

- [ ] Illegal transition from any public mutation throws; no silent patch.
- [ ] `requireAnalyst` rejects non-analista / inactive.
- [ ] Shared tests cover ≥5 illegal transitions + happy paths for main flow.
- [ ] `bun run test` + typecheck green.

## Maintenance

Any new delivery action must update shared map + one mutation + tests in the same PR.
