1/** 2 * T-162/T-163 — Review workflow DTOs. 3 * 4 * The page's `reviewState` machine (A-003: explicit reviewer lists, not 5 * roles): 6 * 7 * published ──submit──▶ in_review ──approve────────▶ published 8 * ▲ │ ▲ 9 * │ │ └── submit (editor resubmits after fixing) 10 * └──changes requested─┘ 11 * cancel (editor) withdraws an in-flight review → published. 12 * archived is orthogonal (the page trash/archive — not a review state). 13 * 14 * The LEGACY default is `published` — pages created before this feature 15 * behave exactly as before (no review banner, no gate). 16 */ 17import { z } from "zod"; 18 19export const REVIEW_STATES = ["draft", "in_review", "approved", "published", "changes_requested"] as const; 20export type ReviewState = (typeof REVIEW_STATES)[number]; 21 22/** 23 * The transition matrix (enforced in `ReviewService` — an illegal move is a 24 * 400 with the allowed set). `actorRole` is the caller's position: 25 * editor — the page's `updatedBy` (or a page-EDITOR grant) 26 * reviewer — a member of the OPEN review's `reviewerIds` 27 * other — anyone else in the space (may READ, not act) 28 */ 29export const REVIEW_ACTIONS: Record<ReviewAction, { from: ReviewState[]; by: "editor" | "reviewer" }> = { 30 /** Editor submits the page for review (opens/reattaches a Review row). */ 31 submit: { from: ["draft", "published", "changes_requested"], by: "editor" }, 32 /** A reviewer approves → the page is published, the review closes. */ 33 approve: { from: ["in_review"], by: "reviewer" }, 34 /** A reviewer sends it back with feedback. */ 35 request_changes: { from: ["in_review"], by: "reviewer" }, 36 /** The editor withdraws an in-flight review (no decision recorded). */ 37 cancel: { from: ["in_review"], by: "editor" } 38} as const; 39 40export const REVIEW_ACTION_VALUES = ["submit", "approve", "request_changes", "cancel"] as const; 41export type ReviewAction = (typeof REVIEW_ACTION_VALUES)[number]; 42 43/** `POST /pages/:id/review` — perform a review action. */ 44export const ReviewActionDto = z.object({ 45 action: z.enum(["submit", "approve", "request_changes", "cancel"]), 46 /** Required for `submit`: the explicit reviewer list (A-003 — stable user ids, ≥1). */ 47 reviewerIds: z.array(z.string().min(1)).max(50).optional(), 48 /** Optional decision comment (stored on the Review row). */ 49 comment: z.string().max(5000).optional(), 50 /** Optional deadline for the decision (ISO date-time). */ 51 deadline: z.string().datetime({ offset: true }).nullable().optional() 52}); 53export type ReviewActionInput = z.infer<typeof ReviewActionDto>; 54