Workflows¶
Every long-lived state machine in SISS is a Temporal workflow in
workflow-svc. Activities call other services via authenticated HTTP.
Temporal provides durable retries, SLA timers, human-task gates (via signals),
and a full replayable history for audit.
Why Temporal (not BPMN / Camunda)¶
- Native Python SDK, first-class activity testing.
- Workflow code is the source of truth; no separate BPMN XML to maintain alongside code.
- Replayable history enables workflow versioning without breaking in-flight submissions.
- Time-travel debugging during support and audits.
KM Submission Workflow¶
This is the core M2 workflow — from PSP pre-consult through SIGL issuance.
flowchart TD
Start([PSP submits])
S1[① Pre-consultation<br/>· PSP completes checklist<br/>· AI admin pre-check<br/>· CMU officer review + SLA gate]
S2[② Intake accepted<br/>fan-out: compliance extraction + BIM validation]
S3a[③ ATD review<br/>draft comments → officer finalise]
S3b[③ ATL review<br/>draft comments → officer finalise]
S3c[③ ...more departments]
Join{all departments closed}
S4[④ Consolidation & Perakuan<br/>· Aggregate comments<br/>· Draft Kertas Perakuan<br/>· ATD/ATL digital signatures<br/>· Generate SIGL]
S5[⑤ Issue & Notify<br/>Publish certificates · Notify all parties]
End([Submission ISSUED])
Start --> S1 --> S2
S2 --> S3a
S2 --> S3b
S2 --> S3c
S3a --> Join
S3b --> Join
S3c --> Join
Join --> S4 --> S5 --> End
S1 -.pushback.-> PSP[PSP revision pending]
PSP -.resubmit.-> S1
Full lifecycle (every signal, activity, timer)¶
The diagram above is the orientation view. The diagram below shows the full lifecycle of one submission: every place the workflow pauses for a human (signal), every call out to another service (activity), every timer, and every decision point.
Legend.
- Diamond — decision (workflow chooses a branch)
- Pink — signal (workflow pauses, waiting for a human action via the SPA)
- Blue — activity (workflow calls another service)
- Yellow — timer (workflow sleeps, then continues)
- Green — terminal state
flowchart TD
Start([PSP starts submission])
%% ① Pre-consultation
CRT[/Activity: create submission<br/>→ submission-svc/]
CHK{{Signal: PSP completes checklist}}
AIPRE[/Activity: AI admin pre-check<br/>→ ai-svc/]
CMUSIG{{Signal: CMU officer decides}}
CMUDEC{pass or pushback?}
PSHBK[Status: PSP_REVISION_PENDING]
RESUB{{Signal: PSP resubmits}}
%% ② Intake accepted, fan out
INTAKE[Status: INTAKE_ACCEPTED]
AIEXT[/Activity: AIExtractCompliance<br/>→ ai-svc/]
BIMVAL[/Activity: BIMValidateIfPresent<br/>→ bim-svc/]
%% ③ Department review fan-out
FAN((Fan out per ATD/ATL))
ATD_DRAFT[/Activity: GenerateDraftComments<br/>→ ai-svc/]
ATD_SIG{{Signal: ATD officer finalises}}
ATD_TIMER[Timer: 2-day SLA]
ATD_REM[/Activity: send reminder<br/>→ notification-svc/]
ATL_DRAFT[/Activity: GenerateDraftComments<br/>→ ai-svc/]
ATL_SIG{{Signal: ATL officer finalises}}
ATL_TIMER[Timer: 2-day SLA]
ATL_REM[/Activity: send reminder<br/>→ notification-svc/]
SIRP_DRAFT[/Activity: GenerateDraftComments<br/>→ ai-svc/]
SIRP_SIG{{Signal: SIRP officer finalises}}
JOIN((Join: all departments closed))
%% ④ Consolidation & Perakuan
AGG[/Activity: AggregateComments<br/>→ comment-svc/]
DRAFT[/Activity: DraftKertasPerakuan<br/>→ ai-svc/]
SIGNER1{{Signal: signatory 1 signs}}
SIGNER2{{Signal: signatory 2 signs}}
SIGNED[/Activity: PAdES sign + KMS<br/>→ signing-svc/]
SIGL[/Activity: GenerateSIGL<br/>→ ai-svc + signing-svc/]
%% ⑤ Issue & notify
PUB[/Activity: PublishCertificates<br/>→ submission-svc/]
NOTIF[/Activity: NotifyAllParties<br/>→ notification-svc/]
Done([Submission ISSUED])
%% Edges — happy path
Start --> CRT --> CHK --> AIPRE --> CMUSIG --> CMUDEC
CMUDEC -->|pass| INTAKE
CMUDEC -->|pushback| PSHBK --> RESUB --> CRT
INTAKE --> AIEXT
INTAKE --> BIMVAL
AIEXT --> FAN
BIMVAL --> FAN
FAN --> ATD_DRAFT --> ATD_SIG
ATD_SIG -.->|2 days no response| ATD_TIMER --> ATD_REM --> ATD_SIG
ATD_SIG --> JOIN
FAN --> ATL_DRAFT --> ATL_SIG
ATL_SIG -.->|2 days no response| ATL_TIMER --> ATL_REM --> ATL_SIG
ATL_SIG --> JOIN
FAN --> SIRP_DRAFT --> SIRP_SIG --> JOIN
JOIN --> AGG --> DRAFT --> SIGNER1 --> SIGNER2 --> SIGNED --> SIGL
SIGL --> PUB --> NOTIF --> Done
%% Styling via per-node `style` directives — emits inline SVG style=
%% attributes, which have highest CSS specificity and work in both
%% light and dark mode regardless of node shape or Zensical's themeCSS.
%% Terminal
style Start fill:#dcfce7,stroke:#15803d,stroke-width:2px,color:#1a1a1a
style Done fill:#dcfce7,stroke:#15803d,stroke-width:2px,color:#1a1a1a
%% Signal (human pauses)
style CHK fill:#fde2e4,stroke:#b91c1c,stroke-width:1.5px,color:#1a1a1a
style CMUSIG fill:#fde2e4,stroke:#b91c1c,stroke-width:1.5px,color:#1a1a1a
style RESUB fill:#fde2e4,stroke:#b91c1c,stroke-width:1.5px,color:#1a1a1a
style ATD_SIG fill:#fde2e4,stroke:#b91c1c,stroke-width:1.5px,color:#1a1a1a
style ATL_SIG fill:#fde2e4,stroke:#b91c1c,stroke-width:1.5px,color:#1a1a1a
style SIRP_SIG fill:#fde2e4,stroke:#b91c1c,stroke-width:1.5px,color:#1a1a1a
style SIGNER1 fill:#fde2e4,stroke:#b91c1c,stroke-width:1.5px,color:#1a1a1a
style SIGNER2 fill:#fde2e4,stroke:#b91c1c,stroke-width:1.5px,color:#1a1a1a
%% Activity (service calls)
style CRT fill:#dbeafe,stroke:#1e3a8a,stroke-width:1.5px,color:#1a1a1a
style AIPRE fill:#dbeafe,stroke:#1e3a8a,stroke-width:1.5px,color:#1a1a1a
style AIEXT fill:#dbeafe,stroke:#1e3a8a,stroke-width:1.5px,color:#1a1a1a
style BIMVAL fill:#dbeafe,stroke:#1e3a8a,stroke-width:1.5px,color:#1a1a1a
style ATD_DRAFT fill:#dbeafe,stroke:#1e3a8a,stroke-width:1.5px,color:#1a1a1a
style ATD_REM fill:#dbeafe,stroke:#1e3a8a,stroke-width:1.5px,color:#1a1a1a
style ATL_DRAFT fill:#dbeafe,stroke:#1e3a8a,stroke-width:1.5px,color:#1a1a1a
style ATL_REM fill:#dbeafe,stroke:#1e3a8a,stroke-width:1.5px,color:#1a1a1a
style SIRP_DRAFT fill:#dbeafe,stroke:#1e3a8a,stroke-width:1.5px,color:#1a1a1a
style AGG fill:#dbeafe,stroke:#1e3a8a,stroke-width:1.5px,color:#1a1a1a
style DRAFT fill:#dbeafe,stroke:#1e3a8a,stroke-width:1.5px,color:#1a1a1a
style SIGNED fill:#dbeafe,stroke:#1e3a8a,stroke-width:1.5px,color:#1a1a1a
style SIGL fill:#dbeafe,stroke:#1e3a8a,stroke-width:1.5px,color:#1a1a1a
style PUB fill:#dbeafe,stroke:#1e3a8a,stroke-width:1.5px,color:#1a1a1a
style NOTIF fill:#dbeafe,stroke:#1e3a8a,stroke-width:1.5px,color:#1a1a1a
%% Timer (SLA sleep)
style ATD_TIMER fill:#fef3c7,stroke:#92400e,stroke-width:1.5px,color:#1a1a1a
style ATL_TIMER fill:#fef3c7,stroke:#92400e,stroke-width:1.5px,color:#1a1a1a
%% Status (named intermediate state)
style INTAKE fill:#f3f4f6,stroke:#374151,stroke-width:1px,color:#1a1a1a
style PSHBK fill:#f3f4f6,stroke:#374151,stroke-width:1px,color:#1a1a1a
How to read this
Every pink diamond is a place where the workflow stops, persists its state to Temporal's database, and waits — sometimes for days — until the right person clicks the right button in the SPA. Then it wakes up and continues. Blue parallelograms are activities — calls out to other microservices, automatically retried by Temporal if they fail. Yellow boxes are timers — the workflow sleeps for a fixed duration and then resumes.
The total wall-clock time from Start to Done is typically
weeks to months. The total CPU time the workflow uses is a few
milliseconds.
End-to-end sequence¶
One cycle across PSP, workflow-svc, ai-svc, comment-svc, signing-svc, and notification-svc.
sequenceDiagram
autonumber
participant PSP
participant SPA
participant SUB as submission-svc
participant WF as workflow-svc
participant AI as ai-svc
participant CMT as comment-svc
participant SIGN as signing-svc
participant NOT as notification-svc
PSP->>SPA: submit documents
SPA->>SUB: POST /submissions
SUB-->>WF: submission.created
SUB-->>NOT: submission.created
NOT-->>PSP: "received" email
WF->>AI: extract compliance (activity)
AI-->>WF: compliance.report.ready
WF->>CMT: draft comments per ATD/ATL
CMT-->>WF: drafts
WF-->>NOT: workflow.step.assigned (per officer)
Note over WF: SLA timer running
CMT->>WF: OfficerFinalizesComments (signal)
WF->>CMT: aggregate
WF->>AI: draft Kertas Perakuan
AI-->>WF: PDF draft
WF->>SIGN: multi-signer perakuan
SIGN-->>WF: perakuan.signed
WF->>SIGN: issue SIGL
SIGN-->>SUB: sigl.certificate.issued
SUB-->>NOT: notify all parties
NOT-->>PSP: certificates ready
State machine view¶
The diagrams above show the workflow as a flow of work and as a sequence of calls. This third view shows it as a state machine — the perspective Temporal itself uses internally. Each state corresponds to a section of the workflow function; each transition is triggered by a signal (human input), an activity completion, or a timer.
stateDiagram-v2
direction TB
[*] --> PreConsultation
state PreConsultation {
[*] --> AwaitingChecklist
AwaitingChecklist --> AdminPrecheck : signal: PSP submits checklist
AdminPrecheck --> AwaitingCMUDecision : activity → ai-svc
AwaitingCMUDecision --> Pushback : signal: pushback
AwaitingCMUDecision --> [*] : signal: accept
Pushback --> AwaitingChecklist : signal: PSP resubmits
}
PreConsultation --> IntakeAccepted
state IntakeAccepted {
[*] --> Fork
state Fork <<fork>>
Fork --> ExtractingCompliance : activity → ai-svc
Fork --> ValidatingBIM : activity → bim-svc
ExtractingCompliance --> Join1
ValidatingBIM --> Join1
state Join1 <<join>>
Join1 --> [*]
}
IntakeAccepted --> DepartmentReview
state DepartmentReview {
[*] --> Fork2
state Fork2 <<fork>>
state ATDBranch {
[*] --> ATDDrafting
ATDDrafting --> ATDAwaitingOfficer : activity → ai-svc
ATDAwaitingOfficer --> ATDReminder : timer: 2 days
ATDReminder --> ATDAwaitingOfficer : activity → notification-svc
ATDAwaitingOfficer --> [*] : signal: ATD finalises
}
state ATLBranch {
[*] --> ATLDrafting
ATLDrafting --> ATLAwaitingOfficer : activity → ai-svc
ATLAwaitingOfficer --> ATLReminder : timer: 2 days
ATLReminder --> ATLAwaitingOfficer : activity → notification-svc
ATLAwaitingOfficer --> [*] : signal: ATL finalises
}
state SIRPBranch {
[*] --> SIRPDrafting
SIRPDrafting --> SIRPAwaitingOfficer : activity → ai-svc
SIRPAwaitingOfficer --> [*] : signal: SIRP finalises
}
Fork2 --> ATDBranch
Fork2 --> ATLBranch
Fork2 --> SIRPBranch
ATDBranch --> Join2
ATLBranch --> Join2
SIRPBranch --> Join2
state Join2 <<join>>
Join2 --> [*]
}
DepartmentReview --> Consolidation
state Consolidation {
[*] --> Aggregating
Aggregating --> Drafting : activity → comment-svc
Drafting --> AwaitingSignatures : activity → ai-svc
AwaitingSignatures --> SigningPerakuan : signal: all signatories consent
SigningPerakuan --> IssuingSIGL : activity → signing-svc (KMS)
IssuingSIGL --> [*] : activity → ai-svc + signing-svc
}
Consolidation --> IssueAndNotify
state IssueAndNotify {
[*] --> Publishing
Publishing --> Notifying : activity → submission-svc
Notifying --> [*] : activity → notification-svc
}
IssueAndNotify --> [*]
Reading the state machine
Each top-level box (PreConsultation, IntakeAccepted, etc.) is a
composite state — what the workflow code calls a "stage." The labels on
transitions are the Temporal primitives that drive them: signal
(waits for human input), activity (calls another service), timer
(sleeps for a fixed duration). The <<fork>> and <<join>> markers
represent the parallel-then-rejoin patterns used in IntakeAccepted and
DepartmentReview.
Child workflows¶
SLAReminder— timer-driven reminders escalating through an assignee chain.RevisionResubmission— handles PSP resubmission after pushback; reuses parent context.ApplicantResponseTracker— tracks PSP replies to comments and signals the parent when the response set is complete.
Replay gates every workflow code change
Production workflow history is captured and replayed against proposed workflow code changes in CI. Any replay failure blocks merge, so in-flight submissions never break on deploy.