Files
lcbp3/specs/003-unified-workflow-engine/quickstart.md
T
admin 2c24991f88
CI / CD Pipeline / build (push) Failing after 6m6s
CI / CD Pipeline / deploy (push) Has been skipped
690503:0135 Update workflow #01
2026-05-03 01:36:37 +07:00

6.9 KiB

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

// 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

// backend/src/modules/workflow-engine/guards/workflow-transition.guard.ts
const DSL_ROLE_TO_CASL: Record<string, string> = {
  '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

// In workflow-lifecycle.tsx
import { FilePreviewModal } from './file-preview-modal';

const [preview, setPreview] = useState<WorkflowAttachmentSummary | null>(null);

// In attachment chip onClick:
<button onClick={() => setPreview(attachment)}>{attachment.originalFilename}</button>

<FilePreviewModal attachment={preview} onClose={() => setPreview(null)} />

Admin DSL Editor — Monaco Setup

// 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

# 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)