flash_checkout.yaml fixture — a realistic checkout service covering order creation, inventory reservation, payment charging, and payment application. By the end you will have a working model, understand every section of the YAML, and know how to interpret the verifier’s output, including the four deliberate gaps the model leaves open.
What you are modeling
The checkout pipeline moves an order through four services:- checkout (
create_order) — creates an order record and publishesOrderCreated - inventory (
reserve_inventory) — listens forOrderCreatedand reserves stock - payments (
charge_payment) — listens forInventoryReservedand charges the card - checkout (
apply_payment) — listens forPaymentCapturedand marks the order paid
topic.order_events, ordered and identified by order_id and event_id respectively.
Step 1 — Declare services
Start your model file with a revision and the three owning services:backend, worker, frontend, job) are descriptive only — they do not imply process boundaries, trust boundaries, or availability guarantees. Use them for human clarity in the visualization.
Step 2 — Declare schemas
Every message and request payload needs a declared schema. The checkout flow uses canonical schemas withcompleteness: complete, meaning the verifier may treat undeclared fields as absent:
event_id field on each event schema is what enables message identity on the topic. Without it you cannot declare a keyed message identity, and without that the verifier cannot establish that duplicate deliveries carry the same logical message.
Step 3 — Declare data models and objects
Persistent state lives in data models. The checkout flow has two:object.stock has a composite identity of (warehouse_id, sku) — two fields together identify one stock record. The identity declaration is what makes selector precision, uniqueness, and locking analysis meaningful.
Step 4 — Declare the topic
The order events topic carries six message schemas, all keyed byorder_id for ordering and by event_id for message identity:
ordering mapping says that messages with the same order_id are delivered in order within a key. The message_identity mapping says that two messages of the same schema with equal event_id values are the same logical message — which is what allows the verifier to reason about duplicate deliveries. Every schema carried by the topic must be covered for the verifier to establish that duplicate deliveries are safe across all admitted message types.
Step 5 — Model create_order
create_order is a request-driven operation. It inserts an order record, establishes a publication intent, and returns the new order’s ID:
tx.create_order.newisdeduplicated_bythe request’sidempotency_key. This means a second attempt under the same key resolves the prior commit instead of inserting a duplicate order and re-establishing the intent.- The
effect_intentcaptures the publication payload atomically with the transaction commit. Even if the process crashes between the commit and the publication step, a retry recovers the intent from the keyed commit and executes it. idempotency_key_propagationon the publication effect declares that the downstreamevent_idcarries the same logical identity as the upstreamidempotency_key. This lineage is what lets the verifier trace the idempotency class fromcreate_orderthrough toreserve_inventory.
Step 6 — Model reserve_inventory
reserve_inventory subscribes to OrderCreated events, reads the current stock, and writes a reservation. Notice the deliberate gap:
tx.reserve_inventory is explicitly not_deduplicated. Its write value derives from a transaction_read result, and transaction-read results are never replay-stable. Both facts together mean the verifier cannot prove the transaction is retry-safe by either route.
Step 7 — Run the verifier
With your model file complete, run:Understanding the four unknown obligations
The four unknown obligations form a cascade rooted intx.reserve_inventory:
reserve_inventory — idempotency: unknown
reserve_inventory — idempotency: unknown
tx.reserve_inventory is not_deduplicated and its write value derives from a transaction_read result. Transaction reads are never replay-stable, so the transaction is not provably retry-safe by either route. A duplicate delivery re-drives the full program and re-encounters the transaction, which may reserve stock a second time. The published InventoryReserved event is also tied to a non-replay-stable intent, so the downstream consumer (charge_payment) may receive a second payment trigger.reserve_inventory — recoverability: unknown
reserve_inventory — recoverability: unknown
The same root cause. If the process crashes after
tx.reserve_inventory commits, a resumed attempt re-encounters the transaction, which cannot resolve via a keyed commit (not deduplicated) and cannot reconstruct naturally (read-dependent write). There is no path back to a terminal.charge_payment — idempotency: unknown
charge_payment — idempotency: unknown
Two independent obstacles. First,
effect.charge_payment.card is not_deduplicated — the payment provider does not guarantee deduplication, so a duplicate execution is distinguishable duplicate work. Second, the match_result on the card charge result at step 2 branches on a result that is not replay-stable, meaning a retry may take the ok arm when the original took err, or vice versa, potentially publishing a different outcome event.create_order — idempotency: unknown
create_order — idempotency: unknown
create_order publishes OrderCreated to topic.order_events, which reserve_inventory consumes. Because reserve_inventory’s idempotency is not proven, the cascade from a duplicate OrderCreated delivery is not established to collapse. The verifier traces work transitively — create_order’s idempotency can only be proven if every downstream consumer’s idempotency is also proven.unknown is an epistemic verdict: the checker could not establish the property, typically because a required fact is absent or unspecified. It is never evidence of a violation. The implementation may be safe — the verifier simply cannot confirm it from the declared facts.Generate the visualization
Generate an HTML visualization with the proof overlay:checkout.html in your browser. The system view will show the four unknown obligations highlighted in amber on the relevant operation nodes. Double-click reserve_inventory to see the exact step and transaction the checker could not discharge.
To fix the unknown obligations, you would need to either add deduplicated_by to tx.reserve_inventory (with a stable key) or restructure the write to avoid depending on a transaction-read result. The card charge gap requires the payment provider to declare deduplication semantics, and the result disposition to be declared terminal on the err variant.