Private Docs

Lego ↔ Orchestration — Master Design

Master design การ integrate Lego Engine (UserService) ↔ Orchestrator ↔ Workflow ↔ Task — Approach A: Saga-Projected Single Status (saga ProcessState = source of truth ช่วง in-flight, Lego mirror)

อัปเดต: 2026-06-15

โจทย์: Orchestration Service ต้องเป็น control plane ที่คุม flow ระหว่าง Lego Engine (UserService) ↔ Workflow ↔ Task ไม่ใช่แค่ “bridge” — และต้องไม่ให้ business logic ของ status progression ฝังซ้ำ 2 service ผลลัพธ์: Approach A — Saga-Projected Single Status (saga ProcessState = source of truth ของช่วง in-flight, Lego mirror) ขอบเขต: 4 service — UserService(Lego) / OrchestratorService / WorkflowService / TaskService Reconciled กับ grill 2026-06-15: ดู CONTEXT.md — 4 conflict + scope resolved ฐานข้อมูลเดิม: 09062026/ (ORCHESTRATION_SERVICE_DESIGN + Lane1/Lane2 + ADR 0031-0033), SA-1557-GAP-ANALYSIS


เอกสารชุดนี้

ไฟล์สำหรับ
00-OVERVIEW.md (ไฟล์นี้)ภาพรวม + sequence diagrams + contract matrix + metrics — อ่านก่อน
CONTEXT.mdglossary + decisions ของ grill (อ่านคู่กัน)
01-userservice-lego.mdDev ฝั่ง Lego (UserService)
02-orchestrator.mdDev ฝั่ง Orchestrator (Lane 2 + saga)
03-workflowservice.mdDev ฝั่ง Workflow (contract = ต้องคุยกับทีม)
04-taskservice.mdDev ฝั่ง Task (contract = ต้องคุยกับทีม)

Scope (grill 2026-06-15): เอกสารชุดนี้ออกแบบ integration contract (จุดที่ service คุยกัน) ส่วน Workflow/Task internals = นอกขอบเขต — contract ของ 2 service นั้น (M3-M5, M9-M14) เป็น ข้อเสนอไปคุยกับทีมเจ้าของ ไม่ใช่ค่าที่ lock ฝ่ายเดียว


1. หลักการ (Approach A)

                  ┌──────────────────────────────────────────────┐
                  │   Orchestrator Saga (ProcessState)            │
                  │   = SOURCE OF TRUTH ของช่วง in-flight         │
                  │   (ไม่ทำ notification — §ownership)           │
                  └──────────────────────────────────────────────┘
                       ▲ events            │ projection + commands
                       │                   ▼
   ┌─────────────┐   ASB/HTTP        ┌─────────────┐
   │  Lego       │◀─────────────────▶│ Workflow    │
   │ (UserService)│   (ผ่าน Orch)    │  + Task     │
   │  = MIRROR   │                   │ = ผู้ลงมือ   │
   │ notify→requestor                │ notify→maker│
   └─────────────┘                   └─────────────┘

Lego เป็นเจ้าของเองเฉพาะ: Draft (ก่อน submit) + Resubmitted (กด resubmit) + automation chain หลัง Approved → Finalized
ช่วง in-flight (InReview/Reviewed) Lego แค่ mirror ตามที่ saga project ลงมา

ทำไมถึงแก้ปัญหา “logic ซ้ำ 2 service”: การตัดสินว่า flow อยู่ขั้นไหนเกิดที่ saga ที่เดียว แล้ว push สถานะลง Lego — Lego ไม่คำนวณ in-flight status เอง


1.5 บทบาทแต่ละ Service (อ่านส่วนของตัวเองได้)

ทุกทีมอ่านแถวของตัวเองเพื่อรู้ว่า “ฉันต้อง produce/consume อะไร + รับผิดชอบอะไร”

ServiceConsume (รับเข้า)Produce (ส่งออก)เป็นเจ้าของไฟล์
Lego (UserService)progress (flow-progress-update) + decision (flow-approval-result) จาก Orchflow-submitted + flow-cancel-requested → Orch · notify requestorstatus machine (mirror in-flight), automation chain, requestor notification01
Orchestratorflow-submitted/flow-cancel-requested (Lego) · Pickup/SentToReviewer/Decision/Cancel (Workflow)progress+decision → Lego · create/rework/close → Task · create/resubmit/force-stop → Workflowsaga ProcessState (= source of truth in-flight), 2-actor tracking, 4-eye gate, idempotency/teardown02
Workflow 🤝HTTP จาก Orch: create / resubmit / force-stopASB → Orch: Pickup, SentToReviewer, Decision (แปลงจาก native APPROVE/REJECT) · notify maker (pool + assignee)phase/step model + assignee, การแปลง native→semantic event, maker notification03
Task 🤝HTTP จาก Orch: create / mark-rework / complete / close(return TaskID)task lifecycle + idempotent close/complete04

ลำดับเหตุการณ์ย่อ (ทุก service เห็นจังหวะของตัวเอง): Lego submitOrch สร้าง Task+WorkflowWorkflow: maker หยิบ→Pickup→ส่ง reviewer→SentToReviewer→ตัดสิน→DecisionOrch project/command กลับ Lego + ปิด TaskLego หมุน status + notify requestor


2. Status Mapping — ตารางกลาง (contract หลัก)

#Lego FlowInstanceStatusSaga ProcessStateTrigger (ทิศทาง)ChannelNotify (ใคร→ใคร)
1Draft(ยังไม่มี saga)Lego local
2SubmittedAwaitingPickupLego→Orch flow-submitted(first)Workflow→maker pool
3InReview ⚠️UnderReviewWorkflow→Orch Pickup → Orch→LegoProgress
4ReviewedUnderConsiderationWorkflow→Orch SentToReviewer → Orch→LegoProgress
5ResubmittedUnderReview (re-enter)Lego-local ตอนกด resubmit(local)Workflow→maker (assignee)
6ApprovedApproved (Completed)Workflow→Orch Decision(Approve) → Orch→LegoDecisionLego→requestor
7RejectedRejected (Completed)Workflow→Orch Decision(Reject) → Orch→LegoDecisionLego→requestor
8ReworkReworkRequestedWorkflow→Orch Decision(Rework) → Orch→LegoDecisionLego→requestor
9CancelledCancelled2 ขา (§6) → Orch fan-outDecisionLego→requestor
10Finalized(Completed แล้ว)Lego local (automation จบ)
Abandoned(ไม่แตะ)Lego local housekeeping
Closedเผื่อไว้ ยังไม่ wire

⚠️ InReview = open item (grill scope): มีที่มาเฉพาะถ้า Workflow มี pickup/claim — ถ้า Workflow role-based อาจ map = “phase แรก IN_PROGRESS” หรือตัดทิ้ง (ดู CONTEXT.md Open item) Persistence: FlowInstance.Status = HasConversion<string>().HasMaxLength(30) → เพิ่ม enum ใหม่ = code-only ไม่มี migration⚠️ แก้จาก plan-phase finding: FlowSnapshotJson เป็น immutable snapshot (D-V2-4, set ครั้งเดียวตอน Create) → เก็บ progress metadata ที่นั่นไม่ได้. metadata ที่ต้องการ: lastProgressAt (ordering guard) + assignedMaker/assignedReviewer (display) — 3 ทางเลือก: (a) jsonb column ใหม่ = migration 1 ตัว · (b) แนะนำ derive lastProgressAt จาก FlowInstanceHistory.OccurredAt ที่มีอยู่แล้ว (no migration) · (c) ไม่ persist (เสีย ordering guard). ตัดสินก่อน execute (ดู 01 §L2)


3. สอง Channel ของ Orch→Lego (แยกขาด)

ChannelTopicสำหรับSide effectผ่าน Validator
Progress projectionflow-progress-update (ใหม่)InReview, Reviewedstatus-only, idempotent❌ ไม่ผ่าน
Decision commandflow-approval-result (เดิม)Approve/Reject/Rework/Cancelautomation/teardown✅ ผ่าน (ขยาย in-flight set)

Resubmitted ไม่อยู่ทั้ง 2 channel — Lego set เองตอนกดปุ่ม (symmetric กับ Submitted) Notification ไม่ผ่าน channel ไหนของ orchestrator — Lego/Workflow ยิงเองตาม audience (ดู §ownership ด้านล่าง)

กฎ ordering (Progress channel): ใช้ occurredAt — apply เฉพาะเมื่อ occurredAt > lastProgressAt และ status ยังไม่ terminal; reviewer→maker ถอยกลับได้ (Reviewed→InReview) จึงไม่ใช้ rank monotonic


3.5 Notification Ownership (data-ownership split) [grill 2026-06-15]

orchestrator ไม่ทำ notification เลย — แต่ละ service ยิงเฉพาะ audience ที่ตัวเองถือ data:

Audienceผู้ยิงdata ที่ถือยิงเมื่อ
RequestorLegorequestorUserId + email + deep link resumeApprove/Reject/Rework/Cancel
Maker poolWorkflowmaker poolงานเข้ากองกลาง (create)
Assignee (Maker)Workflowassignee identityresubmit/resume
Orchestratorไม่ยิงเลย

เหตุผล: “split by audience” ≠ “logic ซ้ำ” — แต่ละฝั่ง notify คนที่ตัวเองมี contact data; ผล: orchestrator ตัด INotifyPort + IMakerDirectoryPort + ตัด Phase-0 dep “maker role name”


4. Sequence Diagrams

4.1 Happy Path — Submit → Approve → Finalized

sequenceDiagram
    autonumber
    participant U as Requestor
    participant L as Lego (UserService)
    participant O as Orchestrator (Saga)
    participant W as Workflow (+Task, +notify maker)
    participant N as Notification

    U->>L: Submit
    L->>L: Status = Submitted
    L-)O: ASB flow-submitted (first)
    Note over O: Saga create → AwaitingPickup
    O->>W: HTTP create task + workflow instance (unassigned)
    W-)N: notify maker pool (Workflow เป็นคนยิง)

    Note over W: Maker หยิบงาน (ถ้ามี pickup — open item)
    W-)O: ASB Pickup (+maker identity)
    Note over O: UnderReview / track MakerUsername
    O-)L: ASB flow-progress-update (InReview)
    L->>L: Status = InReview (mirror)

    Note over W: Maker approve phase 1 = ส่งให้ reviewer
    W-)O: ASB SentToReviewer (+reviewer identity)
    Note over O: UnderConsideration / track ReviewerUsername
    O-)L: ASB flow-progress-update (Reviewed)
    L->>L: Status = Reviewed (mirror)

    Note over W: Reviewer approve phase สุดท้าย
    W-)O: ASB Decision(Approve, decisionId)
    Note over O: Approved / Completed
    O->>W: HTTP complete task
    O-)L: ASB flow-approval-result (APPROVED)
    L->>L: Status = Approved → fire automation chain
    L-)N: notify requestor (Approved) — Lego เป็นคนยิง
    L->>L: automation เสร็จ → Finalized

4.2 Rework Loop — Rework → Resubmit

sequenceDiagram
    autonumber
    participant U as Requestor
    participant L as Lego
    participant O as Orchestrator
    participant W as Workflow
    participant N as Notification

    Note over W: Maker/Reviewer ตีกลับ (REJECT + OnRejectToPhaseOrder>0)
    W-)O: ASB Decision(Rework, targets, note)
    Note over O: ReworkRequested
    O-)L: ASB flow-approval-result (REWORK, targets)
    L->>L: Status = Rework (owner เปิดแก้)
    L-)N: notify requestor (Rework) — Lego ยิง (มี deep link)

    U->>L: แก้ step + กด Resubmit
    L->>L: Status = Resubmitted (local, Rework→Resubmitted)
    L-)O: ASB flow-submitted (IsResubmit=true)
    Note over O: ExpectState ReworkRequested → re-enter UnderReview
    O->>W: HTTP resubmit (resume) — ไม่สร้าง task/wf ใหม่
    W-)N: notify maker (assignee คนเดิม) — Workflow ยิง
    Note over O,W: วนกลับเข้า review cycle

4.3 Cancel — 2 ขา (Lego-initiated + Workflow-initiated)

sequenceDiagram
    autonumber
    participant A as Admin / Background
    participant L as Lego
    participant O as Orchestrator
    participant W as Workflow
    participant T as Task
    participant N as Notification

    rect rgb(235,245,255)
    Note over A,N: ขา A — Lego-initiated (admin@Lego หรือ Lego sweep)
    A->>L: Cancel (post-submit)
    L->>L: Status = Cancelled (optimistic local) + teardown identity
    L-)N: notify requestor (Cancelled) — Lego ยิง
    L-)O: ASB flow-cancel-requested (initiatedBy=Lego, mode)
    Note over O: OnCancel (idempotent) → Cancelled
    O->>T: HTTP close task (idempotent)
    O->>W: HTTP force-stop (idempotent)
    O-)L: ASB flow-approval-result (CANCEL) → no-op (Cancelled แล้ว)
    end

    rect rgb(255,245,235)
    Note over A,N: ขา B — Workflow-initiated (admin@Workflow หรือ Workflow sweep)
    A->>W: Cancel
    W->>W: cancel local
    W-)O: ASB Decision(Cancel) / workflow-cancel
    Note over O: OnCancel (idempotent) → Cancelled
    O->>T: HTTP close task (idempotent)
    O-)L: ASB flow-approval-result (CANCEL)
    L->>L: Status = Cancelled + teardown identity
    L-)N: notify requestor (Cancelled) — Lego ยิง
    end

Double-sweep: ทั้ง 2 ขา sweep พร้อมกัน → OnCancel ตัวที่ 2 = no-op (idempotent) + teardown ports idempotent (รวมถึงทน “target ยังไม่ถูกสร้าง” กรณี submit-then-immediately-cancel)

4.4 Out-of-order Progress (occurredAt guard)

sequenceDiagram
    autonumber
    participant O as Orchestrator
    participant ASB as ASB (no order guarantee)
    participant L as Lego (progress consumer)

    O-)ASB: progress InReview (occurredAt=T1)
    O-)ASB: progress Reviewed (occurredAt=T2, T2>T1)
    ASB-)L: Reviewed (T2) มาก่อน
    L->>L: lastProgressAt = T2 → Status = Reviewed
    ASB-)L: InReview (T1) มาทีหลัง
    L->>L: T1 < lastProgressAt(T2) → IGNORE
    Note over L: reviewer→maker ถอยจริง = occurredAt ใหม่กว่า → apply ได้

5. Saga State Machine (OnboardingApprovalSaga) — 2-actor + 4-eye

stateDiagram-v2
    [*] --> AwaitingPickup: flow-submitted (first)
    AwaitingPickup --> UnderReview: Pickup / track Maker + Progress(InReview)
    UnderReview --> UnderConsideration: SentToReviewer / track Reviewer + Progress(Reviewed)
    UnderConsideration --> UnderReview: (reviewer bounce — occurredAt)
    UnderConsideration --> Approved: Decision(Approve) / +TaskComplete
    UnderReview --> Rejected: Decision(Reject) / +TaskClose
    UnderConsideration --> Rejected: Decision(Reject) / +TaskClose
    UnderReview --> ReworkRequested: Decision(Rework)
    UnderConsideration --> ReworkRequested: Decision(Rework)
    ReworkRequested --> UnderReview: flow-submitted (resubmit) / WorkflowResubmit
    AwaitingPickup --> Cancelled: Cancel
    UnderReview --> Cancelled: Cancel
    UnderConsideration --> Cancelled: Cancel
    ReworkRequested --> Cancelled: Cancel
    Approved --> [*]
    Rejected --> [*]
    Cancelled --> [*]

4-eye gate (enforce ที่ saga): Approve รับเฉพาะ UnderConsideration (Reviewer เท่านั้น — Maker approve เองไม่ได้); Reject/Rework รับ {UnderReview, UnderConsideration} (Maker kick-back ได้)

ของใหม่ใน saga: ProcessState UnderConsideration · inbound event SentToReviewerEvent · outbox event LegoProgressRequested · track MakerUsername + ReviewerUsername (2-actor) · OnPickup project InReview · OnDecision เพิ่ม TaskComplete(Approve)/TaskClose(Reject) · 4-eye ExpectState gate


6. Cancel Coordination — สรุปกฎ

ผู้สั่งLego statussaga?พฤติกรรม
OwnerDraftLego local ล้วน (ยังไม่มี Task/Workflow)
Admin@Lego / Lego sweeppost-submitoptimistic local + publish flow-cancel-requested → Orch fan-out teardown
Admin@Workflow / Workflow sweepWorkflow cancel + publish → Orch fan-out + project CANCEL → Lego

Idempotency 3 ชั้น: (1) OnCancel ignore ถ้า Cancelled แล้ว · (2) teardown ports no-op ถ้าปิดแล้ว · (3) teardown ทน “ยังไม่ถูกสร้าง” · fan-out ทั้ง 3 เสมอ ไม่เช็คผู้เริ่ม post-submit cancel set (Lego validator): {Submitted, InReview, Reviewed, Resubmitted, Rework}


7. Contract Matrix (Metrics — แต่ละ service คุยกันยังไง + ตรงกันไหม)

✅ = มี/ตรงแล้ว · 🔨 = ต้องสร้างใหม่ · ⚠️ = มีแต่ต้องแก้ · 🤝 = contract ต้องคุยกับทีมเจ้าของ (Workflow/Task)

7.1 Message / Endpoint inventory

#ชื่อTransportTopic/PathPublisher/CallerConsumer/CalleePayload (field หลัก)สถานะ
M1flow-submittedASBflow-submitted-for-approvalLego ✅Orchestrator 🔨eventId, flowInstanceId, refNo, flowDefinitionCode, isResubmit⚠️ เพิ่ม consumer (Lego ไม่ต้องใส่ requestorUserId — Lego notify เอง)
M2flow-cancel-requestedASBflow-cancel-requested (ใหม่)Lego 🔨Orchestrator 🔨eventId, flowInstanceId, refNo, initiatedBy, mode, reason🔨
M3PickupASB(ทีม Workflow กำหนด)Workflow 🤝Orchestrator 🔨flowInstanceId/refNo, makerIdentity, occurredAt🤝 + ขึ้นกับ Workflow มี pickup ไหม
M4SentToReviewerASB(ทีม Workflow)Workflow 🤝Orchestrator 🔨flowInstanceId/refNo, reviewerIdentity, occurredAt🤝 (= APPROVE non-final phase)
M5DecisionASB(ทีม Workflow)Workflow 🤝Orchestrator 🔨flowInstanceId/refNo, action, decisionId, reworkTargets, reason, occurredAt🤝 (Approve/Reject/Rework/Cancel — map จาก APPROVE/REJECT+OnRejectToPhaseOrder)
M6flow-progress-updateASBflow-progress-update (ใหม่)Orchestrator 🔨Lego 🔨eventId, flowInstanceId, refNo, progressStatus(InReview/Reviewed), occurredAt🔨
M7flow-approval-resultASBflow-approval-result (เดิม)Orchestrator 🔨Lego ✅⚠️decisionId, flowInstanceId, result(APPROVED/REJECTED/REWORK/CANCEL), reworkTargets, reason⚠️ consumer มี แต่ขาด CANCEL
M9Task createHTTP(ทีม Task)Orchestrator 🔨Task 🤝refNo, … → TaskID🤝 (มี endpoint, ยืนยัน contract)
M10Task reworkHTTP(ทีม Task)Orchestrator 🔨Task 🤝taskId/refNo🤝
M11Task close/completeHTTP(ทีม Task)Orchestrator 🔨Task 🤝taskId/refNo, outcome🤝
M12Workflow createHTTP(ทีม Workflow)Orchestrator 🔨Workflow 🤝document/refNo → WfInstanceId🤝 (ต้องคืน WfInstanceId)
M13Workflow resubmitHTTP(ทีม Workflow)Orchestrator 🔨Workflow 🤝wfInstanceId🤝
M14Workflow force-stopHTTP(ทีม Workflow)Orchestrator 🔨Workflow 🤝wfInstanceId🤝

ตัดออกจาก grill (notification ownership): M8 notification-personal, M15 maker directory — orchestrator ไม่ทำ notification; maker notify = Workflow ภายใน

7.2 Wire vocab — ต้องตรง 2 ฝั่ง

ConceptLego enumWire value (UPPER)Orchestratorตรงกัน?
ApproveTransitionAction.ApproveAPPROVEDDecisionAction.Approve
RejectTransitionAction.RejectREJECTEDDecisionAction.Reject
ReworkTransitionAction.ReworkREWORK / CORRECTION_REQUESTEDDecisionAction.Rework✅ (alias 2 ค่า)
CancelTransitionAction.CancelCANCELDecisionAction.Cancel⚠️ Lego consumer ขาด CANCEL (M7)
ProgressInReview/ReviewedInReview/Reviewed(saga ProcessState map)🔨 channel ใหม่

Workflow native → Decision mapping (ต้องคุยกับทีม Workflow): code ปัจจุบัน Workflow มีแค่ APPROVE/REJECT + phase progression + OnRejectToPhaseOrder. การแปลงเป็น Decision vocab (SentToReviewer/Approve/Reject/Rework) = หน้าที่ Workflow publisher แปลงก่อน publish — ดู 03


8. Operational Metrics (ที่ควร monitor หลัง deploy) — แยกตาม Service

แต่ละทีม monitor metric ของตัวเอง; ตัวที่ cross-service ระบุ owner ไว้

8.1 Lego (UserService)

Metricสัญญาณอันตราย
flow-progress-update apply lag (publish→apply)ASB backpressure / consumer ช้า
Progress message ignored (occurredAt เก่า) rateสูงผิดปกติ = ordering พัง / clock skew
flow-approval-result DLQwire vocab ไม่ตรง (เช่น CANCEL ไม่ map)
requestor notify failureIFlowStatusNotificationPublisher ล่ม
flow-cancel-requested publish failurecancel ไม่ถึง orchestrator → Task/Workflow ค้าง

8.2 Orchestrator

Metricสัญญาณอันตราย
flow-submitted DLQ countconsumer ไม่ทำงาน / flowCode ไม่อยู่ใน OwnedFlowCodes
Saga stuck ใน AwaitingPickup เกิน N นาทีWorkflow ไม่ publish Pickup / ไม่มีคนหยิบ
Saga stuck ใน UnderReview/UnderConsideration เกิน NWorkflow ไม่ publish Decision
Cancel double-fire countปกติ (idempotent) — แต่ track ไว้ดู race
Task/Workflow create retry (idempotency skip)Correlations tracking ทำงานไหม
Outbox message age (pending)OutboxProcessor ตาย

8.3 Workflow 🤝

Metricสัญญาณอันตราย
ASB publish failure (Pickup/SentToReviewer/Decision)publisher ล่ม → orchestrator ไม่รู้ว่า maker/reviewer ทำอะไร → saga ค้าง
event ที่ map native→semantic ไม่ออก (เช่น phase advance แต่ไม่ publish SentToReviewer)mapping logic พลาด → Lego status ไม่ขยับ
create ไม่คืน WfInstanceIdorchestrator correlate ไม่ได้ → force-stop/resubmit ทำไม่ได้
force-stop ที่ไม่ idempotent throwcancel teardown ล้ม (กรณี stop ซ้ำ / ยังไม่ถูกสร้าง)
maker notify failure (pool/assignee)maker ไม่รู้ว่ามีงาน → งานค้างกองกลาง

8.4 Task 🤝

Metricสัญญาณอันตราย
create ไม่คืน TaskIDorchestrator correlate ไม่ได้
close/complete ที่ไม่ idempotentcancel double-sweep / cancel-vs-create race → error แทน no-op
mark-rework ล้มtask ไม่สะท้อนสถานะ rework

อ่าน cross-service: saga ค้าง (8.2) มัก root-cause อยู่ที่ Workflow ไม่ publish (8.3); Task/Workflow ค้าง (8.3/8.4) มัก root-cause ที่ Lego cancel publish ล้ม (8.1)


9. Phase-0 Contract Lock + External Dependencies

ต้อง ratify ก่อนเริ่ม code ขนาน:

  1. Lego↔Orch contract (M1, M2, M6, M7) — field-level (user เป็นเจ้าของ — lock ได้เลย)
  2. Workflow↔Orch contract (M3-M5, M12-M14) + Task↔Orch (M9-M11) 🤝 — คุยกับทีม Workflow/Task:
    • Workflow มี pickup/claim ไหม → ตัดสิน InReview (status list)
    • Workflow แปลง native (APPROVE/REJECT/OnRejectToPhaseOrder) → Decision vocab ที่ไหน
    • Workflow create คืน WfInstanceId
    • Task rework/close/complete endpoints + miss-behavior ของ GET /ref
  3. OwnedFlowCodes จริง — saga ปัจจุบัน = {"FX_ONBOARD"} แต่ flow จริง = NEW_REQUESTORverify + แก้ + disjoint จาก AutoApproveWorker

ตัดออกแล้ว (grill): Maker role name — ย้ายเป็นเรื่องภายใน Workflow (orchestrator ไม่ notify maker)


10. Decisions Log (forks ที่ปิดแล้ว)

#Decisionเลือกเหตุผล
D1สถาปัตยกรรมApproach A saga-projected single statuscontrol plane, ไม่ซ้ำ logic
D2status modelfield เดียว (FlowInstanceStatus +4)ไม่มี migration (string column)
D3Progress vs Decision2 channel แยก topicกัน decision side-effect ยิงตอน progress
D4Resubmittedstatus แยกจริง, Lego-localresubmit → Resubmitted (grill: ตรงคำ user)
D5Notification ownerdata-ownership split — Orch ไม่ยิงเลย (grill)Lego→requestor, Workflow→maker; ตัด INotifyPort/MakerDir
D6Progress orderingoccurredAtreviewer bounce ถอยกลับได้
D7Canceloptimistic local + orchestrator fan-out idempotentUX ตอบสนอง + converge
D8Task close บน decisionOnDecision +TaskComplete/TaskCloseกัน task ค้าง
D9FlowInstanceIdstable ข้าม resubmitResubmitAsync reuse instance เดิม
D10Review actors2-actor (Maker + Reviewer) (grill)4-eye audit; track 2 identity
D11Decision authorityMaker kick-back ได้ (Reject/Rework), approve ไม่ได้ (grill)4-eye — enforce ที่ saga
D12Workflow/Task internalsout of grill scope (grill)contract = ข้อเสนอไปคุยทีม

11. งานแยก 4 service (ดูรายละเอียดในไฟล์ย่อย)

Serviceของใหญ่ไฟล์
Lego (UserService)+4 status, progress consumer, validator widen, CANCEL wire, cancel publisher, +PublishRejected/Cancelled (requestor notify)01
OrchestratorLane 2 (consumers + Lego/Workflow/Task ports — ไม่มี notify port) + saga 2-actor/4-eye02
Workflow 🤝ASB publisher (map native→Decision) + maker notify + HTTP endpoints03
Task 🤝rework/close/complete endpoints04