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.

MarkerMeaning in this diagram
⇢ N06Workflow / saga
✓ N20Human decision / approval
◷ N09Timer / clock source
⊟ N30External system boundary
▰ N21Database / state authority
⊂ N33Policy / principal boundary
▣ P03Durable command / receipt
◷ P15Clock / tick / timer
⇆ P02Request / response
◎ P24Inspection / evidence
→ H02Value flow
⇆ H05Request / reply binding
⊂ H23Scoped 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

RelationPatternParticipants, preserving role positions
reviewH02 — Value flow {#h02--value-flow}provider[0]: workflow.proposal; consumer[0]: human.proposal
decisionH02 — Value flow {#h02--value-flow}provider[0]: human.decision; consumer[0]: workflow.decision
deadlineH02 — Value flow {#h02--value-flow}provider[0]: timer.tick; consumer[0]: workflow.deadline
identityH05 — Request / reply binding {#h05--request--reply-binding}provider[0]: identity.create; consumer[0]: workflow.identity
stateH05 — Request / reply binding {#h05--request--reply-binding}provider[0]: journal.checkpoint; consumer[0]: workflow.state
approvalH23 — 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.

MarkerMeaning in this diagram
⇢ N06Workflow / saga
⊟ N30External system boundary
▰ N21Database / state authority
≈ N07Stream operator
✓ N20Human decision / approval
⊢ N37Claim / evidence record
○ P01Value / immutable snapshot
▣ P03Durable command / receipt
◎ P24Inspection / evidence
→ H02Value flow
⊢ H27Derivation / provenance
⋈ H11Keyed 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

RelationPatternParticipants, preserving role positions
statementH02 — Value flow {#h02--value-flow}provider[0]: bank.statement; consumer[0]: join.external
ledgerH02 — Value flow {#h02--value-flow}provider[0]: ledger.snapshot; consumer[0]: join.internal
mismatchH02 — Value flow {#h02--value-flow}provider[0]: join.findings; consumer[0]: run.findings
reviewH02 — Value flow {#h02--value-flow}provider[0]: run.proposal; consumer[0]: review.proposal
evidenceH27 — Derivation / provenance {#h27--derivation--provenance}provider[0]: run.receipt; consumer[0]: receipt.input
cutH11 — 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)
    )
}