Private Docs

Dev Cluster E2E — Onboarding: Sequence Diagrams ครบทุก Scenario (รันจริงผ่าน API)

E2E onboarding (NEW_REQUESTOR) ขับจริงบน deployed dev cluster ผ่าน API (ข้าม FE) — happy/reject/rework/cancel ครบทุก scenario พร้อม sequence diagram + US Status ladder จาก pod log + JSON · key: OC→US return ทำงานจริง (เดิมเข้าใจผิดว่าค้าง — สาเหตุคือข้าม pickup)

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

เอกสารนี้ = ผลรัน E2E จริงบน deployed dev cluster (gateway gateway-dev.exim.go.th + AKS aks-SupperApp-dev) ขับ ผ่าน API ตรง ข้าม Frontend (call API + monitor pod log) รูปแบบตาม 07-e2e-real-run-sequences — sequence diagram ต่อ scenario · ทุก scenario รัน ≥2 ครั้ง · ladder/journey สร้างจาก pod log จริง (authoritative) driver: Atlas docs/orchestration-integration/28062026/E2E_DRIVER.py + scratchpad/e2e_full.py · JSON journal ต่อ run: scratchpad/e2e_<scenario>_<run>_<refNo>.json


TL;DR

  • Full happy path → US Finalized ขับจริงบน cluster ผ่าน API (fresh signup, OTP via Redis, domain มั่ว) — 2 runs (FX-…00104 / FX-…00105)
  • OC→US return ทำงานจริง — OC publish flow-progress-update (InReview→Reviewed→InApprove) + flow-approval-result (APPROVED) กลับ US ครบ (พิสูจน์จาก OC pod log)
  • 🔑 ไขปริศนา “OC→US ค้าง” ของ session ก่อน: เดิมขับ #50/#53 ข้าม pickup ยิง APPROVE ตรง → saga ค้าง AwaitingPickup → decision โดน gate → US ค้าง. ทำ pickup→approve ครบ = เดินจบทุก rung (operator error ไม่ใช่ deployed defect)
  • 🔑 OTP: email/domain มั่วได้ (@e2e-driver.example) — Entra รับ + NS stamp Redis · email ไม่ออกเลยบน dev (NS ANE gateway พัง, ดู §7) → อ่าน OTP จาก Redis
  • ⚠️ CreateCompanyApplication (post-approval): ต้องมี registerDate+juristicStatus ครบ (Company.Create Guard.AgainstNullOrWhiteSpace) — ขาด → stuck ที่ Approved (= test-data artifact ไม่ใช่ defect; เติมแล้วถึง Finalized)
  • branch: Reject / Rework / Cancel — ดู §4 (รัน ≥2 ครั้ง/scenario)

วิธีอ่าน diagram

  • Actor: Requestor (ยื่น/OTP/submit/resubmit) · Admin·Maker (pickup + APPROVE non-final / Rework / Cancel) · Admin·Approver (pickup + APPROVE final / Reject)
  • เส้น: ->> = HTTP (sync) · -->> = ASB topic (async — ลากตรงถึงผู้ consume, ชื่อ topic บนเส้น)
  • mark: ✅ ขับจริง+verify (pod log/API) · 🟡 best-effort/ไม่ landed · 🔴 พัง
  • ขับผ่าน API ตรง (ข้าม FE): pickup = PATCH /workflow/pickup · approve/reject = POST /workflow/{iid}/action (หรือ /steps/{sid}/action) · ไม่มี FE/SignalR

0. Connection map (bird’s-eye) — cluster จริง

flowchart LR
    REQ([Requestor]):::actor
    MK([Admin Maker]):::actor
    AP([Admin Approver]):::actor
    US[UserService]
    OC[Orchestrator]
    WF[Workflow]
    TS[Task]
    NS[Notification]
    RDS[(Redis)]

    REQ -->|HTTP onboarding / submit| US
    MK -->|HTTP pickup / approve / rework / cancel| WF
    AP -->|HTTP pickup / approve / reject| WF
    US -->|Entra signup → webhook| NS
    NS -->|stamp OTP| RDS
    REQ -.->|read OTP| RDS
    US ==>|flow-submitted-for-approval| OC
    OC -->|HTTP create| WF
    WF -->|HTTP task lifecycle| TS
    WF ==>|workflow-pickup / sent-to-reviewer / approval-pickup / workflow-decision| OC
    OC ==>|flow-progress-update / flow-approval-result RETURN| US
    WF -.->|workflow-maker-notification| NS
    NS -.->|email ANE 🔴 พัง| REQ
    classDef actor fill:#eee,stroke:#333;

==> = ASB topic · --> = HTTP · -.-> = best-effort/พัง


1. OC saga state machine (ที่ขับจริงตามนี้)

stateDiagram-v2
    [*] --> AwaitingPickup: flow-submitted (Requestor Submit)
    AwaitingPickup --> UnderReview: workflow-pickup (Maker PICKUP)
    UnderReview --> UnderConsideration: sent-to-reviewer (Maker APPROVE non-final)
    UnderConsideration --> InApprove: approval-pickup (Approver PICKUP)
    InApprove --> Approved: workflow-decision APPROVE (Approver final)
    UnderReview --> Rejected: workflow-decision REJECT
    UnderConsideration --> Rejected: workflow-decision REJECT
    InApprove --> Rejected: workflow-decision REJECT
    UnderReview --> ReworkRequested: workflow-decision REWORK (Maker)
    AwaitingPickup --> Cancelled: workflow-decision CANCEL
    UnderReview --> Cancelled: workflow-decision CANCEL
    Approved --> [*]
    Rejected --> [*]
    Cancelled --> [*]

บทเรียนหลัก: ทุก APPROVE/decision ต้องมี PICKUP ก่อน (AwaitingPickup→UnderReview ต้อง workflow-pickup; UnderConsideration→InApprove ต้อง approval-pickup). ข้าม pickup = saga ค้าง → decision ถูก gate (= สาเหตุที่ #50/#53 session ก่อน US ค้าง Submitted)


3. ✅ Happy Path — submit → maker → approver → Finalized (verified ×2)

sequenceDiagram
    autonumber
    actor REQ as Requestor
    participant US as UserService
    participant RDS as Redis
    participant OC as Orchestrator
    participant WF as Workflow
    participant TS as Task
    actor MK as Admin·Maker
    actor AP as Admin·Approver

    rect rgb(232,248,232)
    Note over REQ,US: LANE 1 — Submit (fresh signup, domain มั่ว)
    REQ->>US: POST /onboarding/instances {NEW_REQUESTOR, req…@e2e-driver.example} ✅
    REQ->>US: SaveDraft → Entra signup → refNo FX-202606-00104 ✅
    US->>RDS: NS webhook stamp OTP (email ไม่ออก 🔴 ANE) 
    REQ->>RDS: read OtpCode ✅
    REQ->>US: OTP Next ✅ → Juristic→UserMode→Designate→CompanyInfo→Signatory ✅
    REQ->>US: FxRequestorTcConsentStep/Submit → SUBMITTED ✅
    US-->>OC: flow-submitted-for-approval ✅
    Note over OC: saga = AwaitingPickup
    OC->>WF: POST /instances (create) → WF #59 ✅
    WF->>TS: POST /tasks (New) ✅
    end

    rect rgb(232,248,232)
    Note over MK,US: LANE 3 — Maker pickup + APPROVE (non-final) + RETURN progress
    MK->>WF: PATCH /workflow/pickup {AppIds:[refNo], ActionBy:maker} ✅
    WF->>TS: PATCH /tasks/pickup ✅
    WF-->>OC: workflow-pickup (Published AppStatusChanged) ✅
    Note over OC: saga = UnderReview
    OC-->>US: flow-progress-update {InReview} → US=InReview ✅ (OC log)
    MK->>WF: POST /workflow/{59}/action {APPROVE, AdminMaker, taskId} ✅ (504/~100s แต่ commit)
    WF->>TS: POST /tasks (approver task) ✅
    WF-->>OC: sent-to-reviewer (Published SentToReviewer) ✅
    Note over OC: saga = UnderConsideration
    OC-->>US: flow-progress-update {Reviewed} → US=Reviewed ✅ (OC log)
    end

    rect rgb(232,248,232)
    Note over AP,US: LANE 4 — Approver pickup + APPROVE (final) + RETURN result → Finalized
    AP->>WF: PATCH /workflow/pickup {AppIds:[refNo], ActionBy:approver} ✅
    WF-->>OC: approval-pickup (Published ApprovalPickup) ✅
    Note over OC: saga = InApprove
    OC-->>US: flow-progress-update {InApprove} → US=InApprove ✅ (OC log)
    AP->>WF: POST /workflow/{59}/action {APPROVE, AdminApprover, taskId} ✅ (200)
    WF-->>OC: workflow-decision {APPROVE} (Published AppStatusRework|APPROVE|DecisionId) ✅
    Note over OC: saga = Approved
    OC-->>US: flow-approval-result {APPROVED, decisionId} → US=Approved ✅ (OC log)
    US->>US: post-approval chain CreateCompanyApplication (ต้อง registerDate+juristicStatus) → Finalized ✅
    end

3.1 Journey ladder — จาก OC pod log จริง (authoritative, FX-202606-00104 / flow c9f4fc36)

WF  19:14:58  Published AppStatusChanged  → 59      (maker pickup)
OC  19:16:34  [Lego] flow-progress-update InReview
WF  19:16:34  Published SentToReviewer     → 59      (maker APPROVE non-final)
OC  19:16:35  [Lego] flow-progress-update Reviewed
WF  19:16:55  Published ApprovalPickup     → 59      (approver pickup)
OC  19:16:56  [Lego] flow-progress-update InApprove
WF  19:16:57  Published AppStatusRework | Action=APPROVE | DecisionId=635061d8…   (approver APPROVE final)
OC  19:16:57  [Lego] flow-approval-result APPROVED  decision=635061d8…
US            Status: Submitted → InReview → Reviewed → InApprove → Approved → Finalized

US Status ladder (API poll): Submitted → Reviewed → InApprove → Finalized — InReview ไม่ทันจับด้วย API poll (อยู่ในช่วง maker-APPROVE block ~100s) แต่ OC log ยืนยัน InReview ถูก publish (19:16

) → ladder จริงครบทุก rung

3.2 JSON ตัวอย่าง (จาก journal)

// 1) Submit (requestor) → SUBMITTED
POST /userservice-api/.../onboarding/instances/{id}/steps/FxRequestorTcConsentStep/actions/Submit
{"stepData":{"agreed":true,"tcVersion":"1.0"}}
200 {"data":{"status":"SUBMITTED","refNo":"FX-202606-00104"}}

// 2) Maker PICKUP
PATCH /workflow-api/.../workflow/pickup
{"AppIds":["FX-202606-00104"],"ActionBy":"e2e-maker"}
200 {"StatusCode":200,"StatusMessage":"Success"}

// 3) Maker APPROVE (non-final) — 504 ที่ ~100s แต่ WF commit (ตรวจ state)
POST /workflow-api/.../workflow/59/action
{"actionType":"APPROVE","role":"AdminMaker","taskId":55,"userId":"e2e-maker"}
504 (Cloudflare; TakeAction ~100s) · state → DocStatus=Reviewed ✅

// 4) Approver APPROVE (final)
POST /workflow-api/.../workflow/59/action
{"actionType":"APPROVE","role":"AdminApprover","taskId":56,"userId":"e2e-approver"}
200 {"StatusCode":200,"StatusMessage":"Success"}

// 5) US final
GET /userservice-api/.../admin/flow-instances/by-ref-no/FX-202606-00104
200 {"data":{"status":"Finalized"}}

3.3 ผลรัน (×3 — 2 Finalized + 1 diagnostic)

runrefNoWF#US Status ladderผล
1FX-202606-0010356Submitted→Reviewed→InApprove→Approved🟡 ค้าง Approved — CreateCompanyApplication stuck (payload ขาด registerDate/juristicStatus) → เผยว่าต้องเติม field
2FX-202606-0010459Submitted→…→Finalized✅ full (เติม field แล้ว)
3FX-202606-0010562Submitted→…→Finalized✅ full (reproducible)

CreateCompanyApplication note: CreateCompanyApplicationHandler.cs:46Company.Create() ใช้ Guard.AgainstNullOrWhiteSpace(registerDate) + (juristicStatus) → ขาด = throw → step stuck → US ค้าง Approved (retry-stuck ไม่ช่วยเพราะ data ยังขาด). = test-data artifact ไม่ใช่ dev defect · real juristic data (มี registerDate/status) ผ่าน → Finalized


4. Branch scenarios (รันจริง ≥2 ครั้ง/scenario)

4.1 — Reject (approver · terminal) → US Rejected

sequenceDiagram
    autonumber
    actor REQ as Requestor
    participant US as UserService
    participant OC as Orchestrator
    participant WF as Workflow
    actor MK as Admin·Maker
    actor AP as Admin·Approver

    Note over REQ,US: onboarding walk (OTP via Redis) → Submit → flow-submitted → WF #65
    MK->>WF: PATCH /pickup {appId, maker} → Published AppStatusChanged ✅
    Note over OC: saga UnderReview → US=InReview
    MK->>WF: POST /{65}/action {APPROVE, AdminMaker} → Published SentToReviewer ✅
    Note over OC: saga UnderConsideration → US=Reviewed
    AP->>WF: PATCH /pickup {appId, approver} → Published ApprovalPickup ✅
    Note over OC: saga InApprove
    rect rgb(232,248,232)
    Note over AP,US: ★ REJECT (approver)
    AP->>WF: POST /{65}/action {REJECT, AdminApprover} → Published AppStatusRework|Action=REJECT|DecisionId ✅
    Note over OC: saga Rejected
    OC-->>US: flow-approval-result {REJECTED} → US=Rejected ✅
    end
  • WF journey (pod log, FX-…00106): AppStatusChanged→65 · SentToReviewer→65 · ApprovalPickup→65 · AppStatusRework | Action=REJECT | DecisionId=9c2fc392…
  • US ladder: Submitted → Reviewed → Rejected (return landed: US เปลี่ยนเป็น Rejected ได้ก็ต่อเมื่อ consume flow-approval-result REJECTED)
POST /workflow-api/.../workflow/65/action
{"actionType":"REJECT","role":"AdminApprover","taskId":61,"userId":"e2e-approver"}
200 {"StatusCode":200,"StatusMessage":"Success"}
GET .../admin/flow-instances/by-ref-no/FX-202606-00106 → {"data":{"status":"Rejected"}}
runrefNoWF#ladderผล
1FX-202606-0010665Submitted→Reviewed→Rejected
2FX-202606-0010768Submitted→Reviewed→Rejected

4.2 — Rework (maker) → US Rework + WF Resubmit-200

sequenceDiagram
    autonumber
    actor REQ as Requestor
    participant US as UserService
    participant OC as Orchestrator
    participant WF as Workflow
    actor MK as Admin·Maker

    Note over REQ,US: onboarding walk → Submit → flow-submitted → WF #80
    MK->>WF: PATCH /pickup {appId, maker} → Published AppStatusChanged ✅
    Note over OC: saga UnderReview → US=InReview
    rect rgb(232,248,232)
    Note over MK,US: ★ REWORK (maker · POST /{refNo}/rework — resolve by AppId)
    MK->>WF: POST /{FX-…00111}/rework {reworkSteps:[CompanyInfoStep], taskId} → Published AppStatusRework|Action=Rework ✅
    Note over OC: saga ReworkRequested
    OC-->>US: flow-approval-result {CORRECTION_REQUESTED} → US=Rework ✅
    end
    rect rgb(255,245,230)
    Note over WF: ★ WF-side resubmit เท่านั้น (label) — US-side requestor resubmit ไม่ทำ (ต้อง owner JWT)
    REQ->>WF: POST /{80}/resubmit {} → 200 · WF DocStatus=ReSubmitted ✅
    end
  • WF journey (pod log, FX-…00111): AppStatusChanged→80 · AppStatusRework | Action=Rework | DecisionId=72b965b9…
  • OC return (pod log): flow-progress-update InReview · flow-approval-result CORRECTION_REQUESTED decision=72b965b9…
  • US ladder: Submitted → InReview → Rework
  • ⚠️ resubmit leg = WF-state only (POST /workflow/{id}/resubmit flips WF DocStatus→ReSubmitted, re-พิสูจน์ resubmit-200 fix สด) — US-side requestor resubmit + saga OnResubmit→UnderReview return ไม่ได้ทดสอบ (ต้อง requestor reopen + owner JWT, นอก scope API-only)
  • 🔑 rework endpoint resolve ด้วย AppId(refNo) ไม่ใช่ numeric instanceId (ใช้ /{instanceId}/rework ด้วย wf_id → 404 Instance AppId .. not found)
POST /workflow-api/.../workflow/FX-202606-00111/rework
{"userId":"e2e-maker","role":"AdminMaker","taskId":76,"reworkSteps":[{"stepType":"CompanyInfoStep","note":"e2e rework"}]}
200 Success · US=Rework
POST /workflow-api/.../workflow/80/resubmit {} → 200 · WF DocStatus=ReSubmitted
runrefNoWF#ladderผล
1FX-202606-0011180Submitted→InReview→Rework✅ + resubmit-200 (WF DocStatus=ReSubmitted)
2FX-202606-0011283Submitted→InReview→Rework✅ + resubmit-200

4.3 — Cancel (maker) → US Cancelled · 🔴 gateway ไม่ expose /cancel

sequenceDiagram
    autonumber
    actor REQ as Requestor
    participant US as UserService
    participant OC as Orchestrator
    participant WF as Workflow
    actor MK as Admin·Maker

    Note over REQ,US: onboarding walk → Submit → flow-submitted → WF #74
    MK->>WF: PATCH /pickup {appId, maker} → Published AppStatusChanged ✅
    Note over OC: saga UnderReview → US=InReview
    rect rgb(232,248,232)
    Note over MK,US: ★ CANCEL (maker · POST /{id}/cancel) — 🔴 gateway 404, ✅ in-cluster
    MK->>WF: POST /{74}/cancel {taskId, userId, reason} → Published AppStatusRework|Action=CANCEL ✅ (in-cluster)
    Note over OC: saga Cancelled
    OC-->>US: flow-approval-result {CANCEL} → US=Cancelled ✅
    end
  • WF journey (pod log, FX-…00109): AppStatusChanged→74 · AppStatusRework | Action=CANCEL | DecisionId=f21dd3fe…
  • OC return (pod log): flow-progress-update InReview · flow-approval-result CANCEL decision=f21dd3fe…
  • US ladder: Submitted → InReview → Cancelled
  • 🔴 gateway ไม่ expose /cancel: POST gateway/workflow-api/.../workflow/{id}/cancel404 {"statusCode":404,"message":"Resource not found"} (format ไม่ใช่ของ WF app = gateway-level 404). route /cancel มีจริง + ทำงาน in-cluster (port-forward svc/workflow-service → 200 CANCELLED) → public gateway ไม่ route /cancel (ขณะที่ /action /rework /resubmit /pickup expose ครบ) — ต้องเพิ่ม route ที่ gateway · ⚠️ ยังไม่แยกชั้นว่า APIM หรือ ingress (ต้องดู IaC routing config เพื่อ pin)
// via gateway:
POST gateway/workflow-api/.../workflow/74/cancel {...} → 404 {"statusCode":404,"message":"Resource not found"}
// in-cluster (bypass APIM):
POST 127.0.0.1:5701/api/workflow-service/v1/workflow/74/cancel
{"instanceId":74,"taskId":70,"userId":"e2e-maker","reason":"e2e cancel"}
200 {"Result":{"InstanceId":74,"Status":"CANCELLED"}} · US=Cancelled
runrefNoWF#ladderผล
1FX-202606-0010871Submitted→InReview→Cancelled✅ (in-cluster; gateway 404)
2FX-202606-0010974Submitted→InReview→Cancelled✅ (in-cluster; gateway 404)

5. 🔑 สิ่งที่เจอระหว่างทดสอบ (สำคัญ)

#findingimpact
1ทุก decision ต้อง PICKUP ก่อน (PATCH /workflow/pickup {AppIds,ActionBy}) — saga: AwaitingPickup→UnderReview ต้อง workflow-pickup, UnderConsideration→InApprove ต้อง approval-pickupsession ก่อน #50/#53 ข้าม pickup → ค้าง (เข้าใจผิดว่า OC→US พัง) — ที่จริง OC→US ทำงาน
2gateway/APIM ไม่ expose /cancel → 404 (route ทำงาน in-cluster)Cancel ผ่าน public API ไม่ได้ — ต้องเพิ่ม APIM operation
3rework resolve ด้วย AppId(refNo) ไม่ใช่ numeric idใช้ /{wf_id}/rework → 404
4CreateCompanyApplication (post-approval) ต้อง registerDate+juristicStatus (Company.Create Guard)ขาด → ค้าง Approved (test-data artifact ไม่ใช่ defect)
5maker/approver APPROVE ได้ 504 (~100s) เพราะ block downstream — WF commit เสมอ (ตรวจ state ไม่เชื่อ HTTP code)API poll พลาด transient rung (InReview) — pod log authoritative
6email ไม่ออกเลยบน dev (NS ANE gateway, §7)OTP/notification อ่าน Redis/log แทน

6. สรุปผลรวม (11 runs)

scenariorunsrefNoterminalreturn verified
Happy3FX-…00103/00104/00105Approved (data-stuck) / Finalized ×2✅ OC flow-progress-update + flow-approval-result APPROVED
Reject2FX-…00106/00107Rejected🟢 inferred — US=Rejected (OC return log rolled, ไม่ได้ capture ตรง; happy/cancel/rework capture OC log ตรง)
Rework2FX-…00111/00112Rework + WF ReSubmitted✅ OC flow-approval-result CORRECTION_REQUESTED
Cancel2FX-…00108/00109Cancelled✅ OC flow-approval-result CANCEL (in-cluster)

residue: FX-…00103 (Approved-stuck, ขาด data) + FX-…00110 (rework-fail attempt, InReview) = test artifact บน shared dev


7. OTP / Notification — ทำไม email ไม่ออก (NS ANE gateway พัง)

ดูเต็ม: Atlas docs/notification-service/29062026/OTP_EMAIL_SEND_BROKEN_DEV_ROOTCAUSE.md

  • Entra → NS auth-extension/email-otp-send webhook → NS stamp Redis สำเร็จ แต่ส่ง email ผ่าน ANE gateway fail/timeout ทุก recipient (gmail + domain มั่ว เหมือนกัน) → webhook คืน 500 · email ไม่ออกเลยบน dev
  • ทดสอบจึง อ่าน OTP จาก Redis (DEV_Notification:otp:onboarding:{email}:{refCode}.OtpCode) ผ่าน VPN · email/domain มั่วได้
  • maker/approver notification ก็ส่งไม่ออก (ANE เดียวกัน) + role-mapping ยัง NoRecipient