Workflows — worked graph designs
These designs describe intended system behavior and qualification requirements. Inspect the system atlas for named ports and relation participants.
Workflow: human approval with deadline and durable resumption
How can a workflow wait days without losing its decision context?
Inspect this design · Definition JSON · revision 997063eb376e
Diagram legend. Frames are named graph regions. Left/right pins show required/offered participation, not a universal schedule. Filled pins are connected; hollow pins are unbound. A junction card preserves one relation's role slots.
| Marker | Meaning in this diagram |
|---|---|
| ⇢ N06 | Workflow / saga |
| ✓ N20 | Human decision / approval |
| ◷ N09 | Timer / clock source |
| ⊟ N30 | External system boundary |
| ▰ N21 | Database / state authority |
| ⊂ N33 | Policy / principal boundary |
| ▣ P03 | Durable command / receipt |
| ◷ P15 | Clock / tick / timer |
| ⇆ P02 | Request / response |
| ◎ P24 | Inspection / evidence |
| → H02 | Value flow |
| ⇆ H05 | Request / reply binding |
| ⊂ H23 | Scoped authorization |
Logic and ownership
Kotlin reconstructs the transition from checkpoint state and a typed decision; deadline and cancellation are explicit events.
Failure and qualification
- Waiting is checkpointed rather than an unbounded in-memory coroutine.
- An approval is scoped to the reviewed proposal and can expire.
- Resumption must revalidate current external identity state and idempotency.
Hyperedge roles
| Relation | Pattern | Participants, preserving role positions |
|---|---|---|
| review | H02 — Value flow {#h02--value-flow} | provider[0]: workflow.proposal; consumer[0]: human.proposal |
| decision | H02 — Value flow {#h02--value-flow} | provider[0]: human.decision; consumer[0]: workflow.decision |
| deadline | H02 — Value flow {#h02--value-flow} | provider[0]: timer.tick; consumer[0]: workflow.deadline |
| identity | H05 — Request / reply binding {#h05--request--reply-binding} | provider[0]: identity.create; consumer[0]: workflow.identity |
| state | H05 — Request / reply binding {#h05--request--reply-binding} | provider[0]: journal.checkpoint; consumer[0]: workflow.state |
| approval | H23 — Scoped authorization {#h23--scoped-authorization} | principal[0]: human.principal; proposal[1]: workflow.revision; policy[2]: policy.policy |
Kotlin common declaration
Import dev.shibasis.reaktor.portgraph.definition.* for this structural declaration. Behavior stays in ordinary Kotlin and binds through qualified runtime ports and adapters. Declaring a graph does not activate a system.
graphDefinition("workflow-onboarding", "997063eb376e5f1e405f0155f0a5b9e36e96980db950c826c9e0bb7cf715c6e8") {
region("control", "Workflow authority")
region("participants", "External participants")
node("workflow", "Onboarding workflow", "N06", "control", ports = listOf(
PortDefinition("proposal", "P03", "ReviewRequestV1", PortPolarity.Offer),
PortDefinition("decision", "P03", "ReviewDecisionV1", PortPolarity.Require),
PortDefinition("deadline", "P15", "DeadlineV1", PortPolarity.Require),
PortDefinition("identity", "P02", "IdentityCreateV1", PortPolarity.Require),
PortDefinition("state", "P02", "WorkflowCheckpointV1", PortPolarity.Require),
PortDefinition("revision", "P24", "ProposalRefV1", PortPolarity.Neutral)
))
node("human", "Human approval", "N20", "participants", ports = listOf(
PortDefinition("proposal", "P03", "ReviewRequestV1", PortPolarity.Require),
PortDefinition("decision", "P03", "ReviewDecisionV1", PortPolarity.Offer),
PortDefinition("principal", "P24", "PrincipalRefV1", PortPolarity.Neutral)
))
node("timer", "Deadline clock", "N09", "control", ports = listOf(
PortDefinition("tick", "P15", "DeadlineV1", PortPolarity.Offer)
))
node("identity", "Identity provider", "N30", "participants", ports = listOf(
PortDefinition("create", "P02", "IdentityCreateV1", PortPolarity.Offer)
))
node("journal", "Checkpoint authority", "N21", "control", ports = listOf(
PortDefinition("checkpoint", "P02", "WorkflowCheckpointV1", PortPolarity.Offer)
))
node("policy", "Activation policy", "N33", "control", ports = listOf(
PortDefinition("policy", "P24", "PolicyRefV1", PortPolarity.Neutral)
))
relation("review", "H02",
Incidence("workflow", "proposal", "provider", 0),
Incidence("human", "proposal", "consumer", 0)
)
relation("decision", "H02",
Incidence("human", "decision", "provider", 0),
Incidence("workflow", "decision", "consumer", 0)
)
relation("deadline", "H02",
Incidence("timer", "tick", "provider", 0),
Incidence("workflow", "deadline", "consumer", 0)
)
relation("identity", "H05",
Incidence("identity", "create", "provider", 0),
Incidence("workflow", "identity", "consumer", 0)
)
relation("state", "H05",
Incidence("journal", "checkpoint", "provider", 0),
Incidence("workflow", "state", "consumer", 0)
)
relation("approval", "H23",
Incidence("human", "principal", "principal", 0),
Incidence("workflow", "revision", "proposal", 1),
Incidence("policy", "policy", "policy", 2)
)
}
Workflow: settlement reconciliation and exception handling
How does reconciliation distinguish a late receipt from a failed effect?
Inspect this design · Definition JSON · revision 87300dbef737
Diagram legend. Frames are named graph regions. Left/right pins show required/offered participation, not a universal schedule. Filled pins are connected; hollow pins are unbound. A junction card preserves one relation's role slots.
| Marker | Meaning in this diagram |
|---|---|
| ⇢ N06 | Workflow / saga |
| ⊟ N30 | External system boundary |
| ▰ N21 | Database / state authority |
| ≈ N07 | Stream operator |
| ✓ N20 | Human decision / approval |
| ⊢ N37 | Claim / evidence record |
| ○ P01 | Value / immutable snapshot |
| ▣ P03 | Durable command / receipt |
| ◎ P24 | Inspection / evidence |
| → H02 | Value flow |
| ⊢ H27 | Derivation / provenance |
| ⋈ H11 | Keyed temporal join |
Logic and ownership
A Kotlin comparison function classifies missing, duplicate, late and mismatched entries for a stated reconciliation cut.
Failure and qualification
- Absence before the source cut is not proof of permanent failure.
- Correction requires a new mediated ledger operation, not rewriting the evidence.
- Statement provenance, time zones and currency rounding rules belong to the contract.
Hyperedge roles
| Relation | Pattern | Participants, preserving role positions |
|---|---|---|
| statement | H02 — Value flow {#h02--value-flow} | provider[0]: bank.statement; consumer[0]: join.external |
| ledger | H02 — Value flow {#h02--value-flow} | provider[0]: ledger.snapshot; consumer[0]: join.internal |
| mismatch | H02 — Value flow {#h02--value-flow} | provider[0]: join.findings; consumer[0]: run.findings |
| review | H02 — Value flow {#h02--value-flow} | provider[0]: run.proposal; consumer[0]: review.proposal |
| evidence | H27 — Derivation / provenance {#h27--derivation--provenance} | provider[0]: run.receipt; consumer[0]: receipt.input |
| cut | H11 — Keyed temporal join {#h11--keyed-temporal-join} | external[0]: bank.cut; internal[1]: ledger.cut; join[2]: join.window |
Kotlin common declaration
Import dev.shibasis.reaktor.portgraph.definition.* for this structural declaration. Behavior stays in ordinary Kotlin and binds through qualified runtime ports and adapters. Declaring a graph does not activate a system.
graphDefinition("workflow-reconciliation", "87300dbef73781c33e93f67d18576c9e6749e83c2908017c9540af530bba1300") {
region("control", "Workflow authority")
region("participants", "External participants")
node("run", "Reconciliation workflow", "N06", "control", ports = listOf(
PortDefinition("findings", "P01", "MismatchSetV1", PortPolarity.Require),
PortDefinition("proposal", "P03", "ResolutionProposalV1", PortPolarity.Offer),
PortDefinition("receipt", "P24", "ReconciliationReceiptV1", PortPolarity.Offer)
))
node("bank", "Payment statement source", "N30", "participants", ports = listOf(
PortDefinition("statement", "P01", "StatementV1", PortPolarity.Offer),
PortDefinition("cut", "P24", "SourceCutV1", PortPolarity.Neutral)
))
node("ledger", "Internal settlement ledger", "N21", "control", ports = listOf(
PortDefinition("snapshot", "P01", "SettlementSnapshotV1", PortPolarity.Offer),
PortDefinition("cut", "P24", "SourceCutV1", PortPolarity.Neutral)
))
node("join", "Temporal join", "N07", "control", ports = listOf(
PortDefinition("external", "P01", "StatementV1", PortPolarity.Require),
PortDefinition("internal", "P01", "SettlementSnapshotV1", PortPolarity.Require),
PortDefinition("findings", "P01", "MismatchSetV1", PortPolarity.Offer),
PortDefinition("window", "P24", "WindowSpecV1", PortPolarity.Neutral)
))
node("review", "Exception review", "N20", "participants", ports = listOf(
PortDefinition("proposal", "P03", "ResolutionProposalV1", PortPolarity.Require)
))
node("receipt", "Reconciliation evidence", "N37", "control", ports = listOf(
PortDefinition("input", "P24", "ReconciliationReceiptV1", PortPolarity.Require)
))
relation("statement", "H02",
Incidence("bank", "statement", "provider", 0),
Incidence("join", "external", "consumer", 0)
)
relation("ledger", "H02",
Incidence("ledger", "snapshot", "provider", 0),
Incidence("join", "internal", "consumer", 0)
)
relation("mismatch", "H02",
Incidence("join", "findings", "provider", 0),
Incidence("run", "findings", "consumer", 0)
)
relation("review", "H02",
Incidence("run", "proposal", "provider", 0),
Incidence("review", "proposal", "consumer", 0)
)
relation("evidence", "H27",
Incidence("run", "receipt", "provider", 0),
Incidence("receipt", "input", "consumer", 0)
)
relation("cut", "H11",
Incidence("bank", "cut", "external", 0),
Incidence("ledger", "cut", "internal", 1),
Incidence("join", "window", "join", 2)
)
}