Skip to content
← Free FDE training guides

Free learning / 50-minute transaction proof lab

Save the reviewed note, not a stale decision

Use two real SQLite connections to prove stale review, overlapping deliveries, durable retries and rollback. Explain which facts still support a new internal note.

No account needed. Use your own copy for writing and keep the original as a reference.

Engineering lab library / Run locally

Keep the review valid through the write.

Predict seven outcomes, reproduce a stale-read defect and inspect real lock contention, rollback and historical receipts.

Real local SQLite with two connections and invented case records. Python 3.11+; no network, banking access or platform writes.

  1. Start here / Python 3.11+ · Standard library

    Financial handoff transaction lab

    Run seven cases, move one read across the transaction boundary and inspect actual committed rows.

    Python 3.11+ · standard library · disposable SQLite databases

A financial-services FDE proof lab · about 50 minutes · Python 3.11+ standard library.

A case handler approves an internal note that says a temporary credit was recorded. Before your application saves it, another worker updates the case. Your note passed review, but does that review still support writing it now?

This workshop turns that gap into running code. You use two connections to a disposable SQLite database, inspect actual committed rows, deliberately move one check to the wrong place and rehearse the receiving decision. All records are invented. No banking account, model, network call or customer data is involved.

1. Understand the work before the transaction

A debit-dispute case records an investigation. A provisional credit is temporary; it is not the final outcome. The note in this exercise records one observed credit state for internal case operations. Saving it does not transfer money, determine liability, contact a customer or close the investigation.

In the previous pagination lab, you established a complete source snapshot. Completeness and freshness are different: a coherent snapshot can become old before a write. Here, the case row and the note live in one local database so you can study that exact boundary.

A case revision identifies a version of the case. A source revision identifies the version of the supporting observation. The fictional review binds both revisions and the exact requested statement. Think of reviewing a particular Git diff: approval of one diff does not approve a different patch merely because it uses the same branch name.

UNDERSTAND THE SYSTEM

Approval has a version. So does the case.

Follow an internal case note from review to commit. The database boundary decides whether the reviewed facts still support a new write; a retry returns evidence of an earlier write.

FOLLOW ONE EXAMPLE

See what moves and why.

Authored illustration
01 / EXPLORE THE PARTSTap a part to explain it
Direction of information or workReturn / feedback

WHAT THIS PART DOES

Reviewed note

The reviewer accepted this exact statement against case revision 1 and source revision 1. Approval does not automatically cover a later case.

CONNECTED TO
  • Time passes: Case changes
Read the connections in plain English
  1. 01
    Reviewed note → Case changes

    The accepted draft and the current case can diverge.

  2. 02
    Case changes → Reload inside the transaction

    The safe reader observes revision 2 under the acquired write transaction.

  3. 03
    Reload inside the transaction → Does the review still apply?

    The transaction protects this database comparison from a competing local write.

  4. 04
    Does the review still apply? → Receiving team

    No note is inserted. Do not rewrite temporary as final without a new approval.

  5. 05
    Does the review still apply? → Stored note and receipt

    When all evidence still matches, the note and operation identity commit together.

  6. 06
    Stored note and receipt → Receiving team

    An unchanged operation returns the same historical receipt with no additional row.

Check your understanding: A retry returns a note at revision 1 while the case is now revision 2. Is the retry broken?

Not necessarily. An identical retry should return the earlier committed result without another write. The receipt must be presented as history. It cannot establish the current credit state or authorize a fresh action.

2. Give the database words a concrete meaning

TermWhat it means hereWhat to inspect
TransactionA boundary around the read, decision and writeBEGIN, comparison, INSERT and COMMIT in the trace
CommitMake the note durable in this scratch databaseA second connection can read the stored row
RollbackDiscard this transaction's unfinished changesThe injected failure leaves no note
ContentionTwo connections want the write boundary at the same timeThe second receives SQLite's busy result
Idempotency keyThe identity of one intended operationThe unique pair of workspace and operation ID
ReceiptThe committed result of that operationExact statement and versions from the earlier save

The safe writer acquires BEGIN IMMEDIATE, checks for an existing operation and then reloads the case before inserting a new note. The local database permits one writer at a time. It is the placement of the read inside that boundary that matters; writing BEGIN somewhere in the function does not make an earlier read current.

The deliberate --check-before-lock mode moves only the case read outside the boundary. The review, wording comparison, operation uniqueness and rollback remain in place. This isolates a timing defect without making every check disappear.

3. Run the transaction proof lab

Download fde-financial-handoff.py into a new folder. It needs Python 3.11 or later; check python3 --version first and use your installed 3.11+ interpreter if python3 points to an older version. The script creates and removes its own temporary databases. The reports below are files you choose to keep.

Code example
python3 fde-financial-handoff.py
python3 fde-financial-handoff.py --json > handoff-guarded.json
python3 fde-financial-handoff.py --check-before-lock --json > handoff-before-lock.json

Before opening the reports, predict which cases save a new row and which should return no new write.

CaseEvidence to compareQuestion to answer
supported-noteReview, source revisions and committed rowWhat exactly was saved?
source-changed-before-writeB's committed update before A's transactionCan the old approval support a new note?
changed-reviewed-contentProposed statement versus accepted requestDoes approval still match the sentence?
identical-retry-after-source-changeEarlier receipt versus current sourceIs the receipt history or a fresh observation?
changed-operationSame operation ID, different requestShould a new intent replace the old result?
rollback-and-retryRows after the injected failure and after retryDid an uncommitted insert leave a durable note?
overlapping-deliveriesChronological timeline and both connectionsDid the second writer actually encounter a lock?

Each attempt contains its exact request and review, source and note rows before and after, an outcome, the checks actually reached and any historical receipt. The top-level timeline is chronological. In the overlap case, connection B's first attempt happens inside A's insert hook; the attempts list groups the complete A result before B's busy result. Use the timeline to understand their actual order.

Inspect source-changed-before-write in both reports. The source and review are unchanged between runs. Explain why using a captured revision lets the broken writer save an old statement even though it starts a transaction later. Do not repair this by silently changing the sentence to final credit: that sentence has not been reviewed.

4. Follow two deliveries through one database

The overlap case uses two real SQLite connections to the same file. A inserts while holding its transaction. Its hook invokes B, which attempts BEGIN IMMEDIATE with a zero lock-wait timeout. The database returns busy. A then commits; the caller makes one bounded retry through B and reads the stored receipt. There are no sleeps or fabricated lock outcomes.

Text example
Connection A: acquire → inspect → INSERT ───────────→ COMMIT
Connection B:                         try → BUSY              retry → receipt
Committed notes: 0                    0              1                  1

This controlled ordering is an experiment, not a load test. It establishes what happens when these two transaction lifetimes overlap. It does not measure throughput, fairness, process crashes or every scheduling order. In production, bound retries by time and count, apply an appropriate backoff policy and preserve the original operation identity. Do not retry every error indefinitely.

For rollback, the injected exception happens after INSERT and before COMMIT. The script explicitly rolls back and then retries from the second connection. Read the actual rows after the failure. An exception message by itself is not proof that storage returned to the expected state.

5. Change an input and prove a new boundary

Importing the file does not run the scenarios. write_note is the small decision boundary; approved creates an explicitly trusted review fixture. Never expose that fixture helper as an endpoint that lets generated content approve itself.

In a copy of the lab, change only source_revision in connection B before the write. Leave the case revision and wording unchanged. Predict whether the original review is still sufficient, then inspect the guarded output. Next change only the case revision. Each version has an independent meaning.

Save this as source-only.py beside the lab and run it with your Python 3.11+ interpreter. It exercises a new ordering directly through the same writer, using a separate temporary database.

Code example
import runpy
from copy import deepcopy
from pathlib import Path
from tempfile import TemporaryDirectory

lab = runpy.run_path("fde-financial-handoff.py")
with TemporaryDirectory() as folder:
    path = Path(folder) / "source-only.db"
    a, b = lab["connect"](path), lab["connect"](path)
    try:
        lab["initialize"](a)
        request = deepcopy(lab["REQUEST"])
        review = lab["approved"](request)
        def change_only_the_source():
            b.execute("UPDATE cases SET source_revision=2")
        result = lab["write_note"](a, request, review,
                                   before_transaction=change_only_the_source)
        assert result["outcome"] == "stale_review"
        assert lab["notes"](b) == []
        assert lab["source"](b, request)["case_revision"] == 1
        print("Changed source, unchanged case: no stale note committed.")
    finally:
        a.close()
        b.close()

Try a review for a different case, or replace case_operations with an unrelated role. Inspect the stopping reason and prove no note was added. The trusted workspace is a separate input from the request's workspace; change the request and prove the scope check still runs even for a retry.

Finally, modify the overlap hook so A rolls back after B receives busy. What should B's retry do when no receipt exists? Show the rows and timeline. A valid retry may now create the one note instead of returning a receipt. Keep that distinction in the receiving explanation.

6. Hand over evidence a colleague can use

Prepare a short review with a peer or your existing facilitator. If working alone, mark the exercise self-rehearsed. This workshop does not book mentorship or certify an assessment.

Bring both reports and one unfamiliar fixture. Ask the receiver to identify the stopping boundary without seeing your explanation first. Then ask them to distinguish three tasks: refresh stale review with case operations, retry a busy delivery within policy, and investigate a reused operation key with changed content. Record any assistance they needed and the exact repair requested.

Keep the receipt's historical statement and revisions visible. A returned receipt says the earlier operation committed; it does not say the current case still has temporary credit. A new business decision needs fresh evidence and a separately identified, reviewed operation.

Before connecting a real service, document the unproven boundaries: authenticated identity and reviewer authority; atomicity across the actual source and destination; source version semantics; the target database's isolation and retry behavior; durable recovery after process failure; and any external delivery receipt. A SQLite lock cannot protect a banking API that lives outside the transaction. Do not hold the transaction open for a model call or a human decision. For a remote source, agree a supported version-precondition or handoff protocol with its owner and test that contract separately.

7. Read the primary references

Technical references checked 23 October 2026:

Continue with the receiving review sheet, carrying the actual rows, timelines and unresolved integration questions.

Before you move on

Keep the evidence of your work.

  • Two reproducible reports isolating a stale-read defect.
  • A real overlapping-transaction timeline and an unfamiliar rollback ordering.
  • A receiving decision that separates past receipts from current evidence.

Keep these in your own working copy. Reading this page does not save work or record completion.

Suggested next free session

Review a portfolio with a receiver

Hand over the actual rows, transaction timeline and unresolved integration questions. This is a suggested sequence; choose the practice that addresses your current gap.

Open the next free guide
Have Academy access?

Continue with worked explanations, practice and review in the related lesson.

Open the related Academy lesson →
Back to top ↑