# Quickstart: Unified Workflow Engine — Production Hardening **Phase 1 Output** | Generated: 2026-05-02 **For**: Developers implementing tasks from `tasks.md` (generated by `/speckit-tasks`) --- ## Pre-flight Checklist Before writing any code: - [ ] Apply Delta 09: `specs/03-Data-and-Storage/deltas/09-add-version-no-to-workflow-instances.sql` - [ ] Apply Delta 10: `specs/03-Data-and-Storage/deltas/10-add-action-by-user-uuid-to-workflow-histories.sql` - [ ] Confirm `workflow_instances` has `version_no` column: `DESCRIBE workflow_instances;` - [ ] Confirm `workflow_histories` has `action_by_user_uuid` column: `DESCRIBE workflow_histories;` - [ ] Verify existing tests pass: `pnpm test --testPathPattern=workflow-engine` --- ## Implementation Order Tasks MUST be implemented in this order to avoid breaking existing functionality: ``` [B1] Schema Deltas (DB) ↓ [B2] Entity + DTO updates ↓ [B3] processTransition() — optimistic lock ↓ [B4] WorkflowTransitionGuard — CASL role mapping ↓ [B5] Observability — metrics + structured log ↓ [B6] DSL Redis cache invalidation ↓ [B7] BullMQ DLQ + n8n webhook ↓ [F1] FilePreviewModal component ↓ [F2] Step-attachment upload zone in IntegratedBanner ↓ [F3] Module gap-fill (all 4 modules) ↓ [F4] Admin DSL editor UI ``` --- ## Key Files Reference | Task | File | Action | |------|------|--------| | B1 | `specs/03-Data-and-Storage/deltas/09-*.sql` | CREATE | | B1 | `specs/03-Data-and-Storage/deltas/10-*.sql` | CREATE | | B2 | `backend/src/modules/workflow-engine/entities/workflow-instance.entity.ts` | EDIT — add `versionNo` | | B2 | `backend/src/modules/workflow-engine/entities/workflow-history.entity.ts` | EDIT — add `actionByUserUuid` | | B2 | `backend/src/modules/workflow-engine/dto/workflow-history-item.dto.ts` | EDIT — add `actorUuid` | | B3 | `backend/src/modules/workflow-engine/workflow-engine.service.ts` | EDIT — optimistic lock, rollback, metrics | | B4 | `backend/src/modules/workflow-engine/guards/workflow-transition.guard.ts` | EDIT — DSL role → CASL | | B5 | `backend/src/modules/workflow-engine/workflow-engine.module.ts` | EDIT — register metrics providers | | B6 | `backend/src/modules/workflow-engine/workflow-engine.service.ts` | EDIT — cache set/del in createDefinition/update | | B7 | `backend/src/modules/workflow-engine/workflow-event.service.ts` | EDIT — DLQ + n8n webhook | | F1 | `frontend/components/workflow/file-preview-modal.tsx` | CREATE | | F2 | `frontend/components/workflow/integrated-banner.tsx` | EDIT — upload zone | | F2 | `frontend/components/workflow/workflow-lifecycle.tsx` | EDIT — attachment chips | | F3 | `frontend/app/(admin)/admin/doc-control/correspondence/[uuid]/page.tsx` | EDIT — banner wiring | | F3 | `frontend/app/(admin)/admin/doc-control/rfa/[uuid]/page.tsx` | EDIT — step-attach gap | | F3 | `frontend/app/(admin)/admin/doc-control/transmittals/[uuid]/page.tsx` | EDIT — step-attach gap | | F3 | `frontend/app/(admin)/admin/doc-control/circulation/[uuid]/page.tsx` | EDIT — step-attach gap | | F4 | `frontend/app/(admin)/admin/workflows/definitions/page.tsx` | CREATE | | F4 | `frontend/app/(admin)/admin/workflows/definitions/[id]/page.tsx` | CREATE | --- ## Critical Patterns ### Optimistic Lock — Client Side ```typescript // Frontend: store versionNo from GET /workflow-engine/instances/:id const { data: instance } = useWorkflowInstance(instanceId); // On transition: pass versionNo in body await triggerTransition({ action: 'APPROVE', versionNo: instance.versionNo, // ← MUST include attachmentPublicIds: pendingFiles, comment, }); // On 409 → show toast "เอกสารถูกอนุมัติโดยผู้อื่นแล้ว กรุณารีเฟรช" // Invalidate query cache → user sees updated state ``` ### DSL Role Mapping — Guard ```typescript // backend/src/modules/workflow-engine/guards/workflow-transition.guard.ts const DSL_ROLE_TO_CASL: Record = { 'Superadmin': 'system.manage_all', 'OrgAdmin': 'organization.manage_users', 'ContractMember': 'contract.view', 'AssignedHandler': '__assigned__', }; // In canActivate: extract require.role from DSL compiled state const stepConfig = compiled?.states?.[instance.currentState]; const requiredRoles: string[] = stepConfig?.require?.role ?? []; for (const dslRole of requiredRoles) { const caslAbility = DSL_ROLE_TO_CASL[dslRole]; if (!caslAbility) continue; if (caslAbility === '__assigned__') continue; // handled by Level 3 check if (userPermissions.includes(caslAbility)) return true; } // Fall through to Level 3 (assignedUserId) check as before ``` ### File Preview Modal — Usage ```tsx // In workflow-lifecycle.tsx import { FilePreviewModal } from './file-preview-modal'; const [preview, setPreview] = useState(null); // In attachment chip onClick: setPreview(null)} /> ``` ### Admin DSL Editor — Monaco Setup ```tsx // In definitions/[id]/page.tsx import dynamic from 'next/dynamic'; const MonacoEditor = dynamic(() => import('@monaco-editor/react'), { ssr: false }); // Validate on change (debounced 800ms) const handleEditorChange = useCallback( debounce(async (value: string) => { try { const parsed = JSON.parse(value); const result = await validateDsl(parsed); setValidationErrors(result.errors); } catch { setValidationErrors([{ path: 'root', message: 'Invalid JSON' }]); } }, 800), [] ); ``` --- ## Testing Verification Commands ```bash # Backend unit tests for workflow engine cd backend pnpm test --testPathPattern=workflow-engine --coverage # Frontend typecheck cd frontend pnpm tsc --noEmit # Frontend component tests cd frontend pnpm vitest run components/workflow # Full backend test suite cd backend pnpm test --coverage ``` --- ## Environment Variables | Variable | Required | Description | |----------|----------|-------------| | `N8N_WEBHOOK_URL` | Prod only | URL for dead-letter job ops notifications | | `REDIS_URL` | All | Redis connection for BullMQ + cache | Both must be set in `docker-compose.yml` — never hardcoded. --- ## Commit Message Convention ``` feat(workflow-engine): add optimistic lock version_no (FR-002, ADR-001 v1.1) feat(workflow-engine): add CASL DSL role mapping to guard (FR-002a) feat(workflow-engine): structured transition log + metrics (FR-022/023) feat(workflow-engine): DSL cache invalidation on activate (FR-007) feat(workflow-engine): BullMQ DLQ + n8n webhook (FR-005/006) feat(workflow-ui): FilePreviewModal component (FR-020) feat(workflow-ui): step-attachment upload zone in IntegratedBanner (FR-014-019) feat(workflow-ui): Admin DSL editor page (FR-024/025) feat(correspondence): IntegratedBanner gap-fill wiring (FR-011) chore(schema): delta-09 version_no, delta-10 action_by_user_uuid (ADR-009) ```