Money mutation without idempotency key

errormoney-mutation-without-idempotency-key

A call that moves money is retried after a lost response. Without an idempotency key on that hop the retry becomes a second charge, which is exactly what the design's exactly-once-effect promise rules out.

Severity
error
Selects
every edge
Components in scope
2,366
What it checks

It looks at every edge, and fires when:

  • — all of these hold:
  • — the edge's target node:
  • — any of these hold:
  • — the node's `handlesMoney` is true
  • — the node's `doubleEntry` has a value
  • — the edge's `idempotent` has no value
  • — the design requirement `correctness.exactlyOnceEffect` is true
Message
Edge '{{edge.from}}' -> '{{edge.to}}' moves money but does not declare idempotent, while the design requires exactly-once effect. Add an idempotency key to this hop.

{{node.id}} style paths are filled in from the node, the edge or the design the finding is about.

Applies to · 2,366 components

This rule is about an edge, so it belongs to a pair of nodes rather than one component.

This rule names no kind: it reads the design as a whole.
Tripped by · 7

Reference designs whose broken twin names this rule in its header. The gate reads the same header, and fails when a rule no design trips.

Source
# yaml-language-server: $schema=../schema/rule.schema.json
# A call that moves money (to a third party that handles money, or into a double-entry
# ledger) will be retried after a lost response. Without an idempotency key on that hop
# the retry becomes a double charge, which the design promises never happens.
# Tripped by: designs/payment-system.broken.yaml
schemaVersion: "1"
type: "rule"
id: "money-mutation-without-idempotency-key"
severity: "error"
select:
  edge: {}
when:
  all:
    - to:
        any:
          - prop: "handlesMoney"
            equals: true
          - prop: "doubleEntry"
            exists: true
    - field: "idempotent"
      exists: false
    - requirement: "correctness.exactlyOnceEffect"
      equals: true
message: "Edge '{{edge.from}}' -> '{{edge.to}}' moves money but does not declare idempotent, while the design requires exactly-once effect. Add an idempotency key to this hop."

The same page as markdown, for an agent: /catalogue/rules/money-mutation-without-idempotency-key.md