The executable contract
The workflow module uses SQLite, JSON and temporary directories from Python's standard library. Download it beside test_pattern_07.py. Run py -3 -B pattern_07.py on Windows or python3 -B pattern_07.py on macOS with Python 3.11 or newer. The code was executed on Windows; the macOS invocation is supplied for portability and was not separately exercised.
The deterministic output contains step equal to two, provider_calls equal to one and receipts equal to one. The demonstration deliberately closes its database connection after recording evidence, constructs a new Workflow instance against the same file, and completes the report. It then invokes completion again. Those actions show persistence and repeat handling without a daemon, cloud account, model credential or background service.
Architecture and request sequence
Read the architecture as text
- start / status: Require matching version
- advance step 0: Call inspection adapter
- Evidence checkpoint: Commit validated result
- advance step 1: Use recorded summary
- Local receipt: Complete in same transaction
- start / status → advance step 0: step zero
- advance step 0 → Evidence checkpoint: commit
- Evidence checkpoint → advance step 1: resume
- advance step 1 → Local receipt: atomic local write
Read the sequence as text
- Caller → Workflow: Start incident run
- Workflow → Provider: Inspect synthetic incident
- Workflow → SQLite: Save evidence and step
- Caller → Workflow: Reopen and advance
- Workflow → SQLite: Write one receipt
Inspect the persisted representation
The runs table contains a primary-key run identifier, workflow version, integer step and serialized evidence. Receipts has one row per completed run. Workflow.start inserts a new row only if the identifier is absent, then reads it through status. Status rejects an unknown run and checks that the persisted workflow version matches the code's selected version before decoding evidence.
This guard is intentionally placed on reading state, not just on starting a run. A restarted worker must not bypass compatibility checks because the run already exists. The module does not implement migrations, branching or cancellation. Adding any of those changes the state contract and deserves new tests. Avoid using an arbitrary integer step from a model response; the coordinator, not the provider, owns which transition is legal.
Follow the first transition
Workflow.advance opens a write transaction and loads the current state. At step zero it calls adapter.inspect with the run identifier. FakeInspector counts its calls and returns a fixed summary and source. The coordinator verifies that the result is a dictionary containing a string summary, serializes it, and advances to step one. This minimal schema is sufficient for the fixture, not a general evidence-validation policy.
A real inspection adapter should return bounded fields such as observation time, source revision and evidence locator, with explicit error categories. Do not let a provider return a destination table, arbitrary executable command or approval decision under the guise of evidence. The adapter seam allows model-backed inspection later, but the current implementation is a deterministic rule demonstration and makes no claim that a model reasoned about an incident.
Follow local completion and recovery
At step one, advance builds a report body from the stored summary, inserts one receipt under the run identifier and moves to step two. Both operations occur in the same database transaction. At step two, further calls perform no new provider inspection and create no additional receipt. The stable run identity is what makes completion repeatable within this local representation.
The crash flag raises after transition statements but before commit. A failed first transition leaves step zero; a failed second transition leaves step one and no receipt. The first case may still increment FakeInspector.calls because Python-side observation is outside SQLite's rollback domain. This is the most important teaching point in the module: rolling back a checkpoint does not undo everything that happened while computing it.
Read the failure-oriented tests
Run py -3 -B -m unittest test_pattern_07, substituting python3 on macOS. Eight tests cover restart after evidence, repeated reading after an injected crash, receipt rollback, completion as a no-op, incompatible workflow versions, missing runs, invalid provider evidence and expected output. These tests assert state and counts, not just that a function returned successfully.
The read-retry test expects two provider calls after a failed first transition and a successful retry. Do not change that expectation to one by hiding a failed call from the counter. If a real provider charges for both attempts, both belong in cost accounting. Likewise, the invalid-provider test checks that malformed evidence cannot advance the workflow. A plausible-looking narrative is not sufficient to satisfy a typed transition contract.
A deployment reference, not a deployment claim
For a hosted version, separate a run API, durable state service, worker scheduler and provider adapter. Authenticate the caller at the run API and bind each run to its tenant and authorization context. Replace the teaching transaction-around-provider approach with a lease and compare-and-swap transition before adding slow network dependencies. Keep a reconciliation path for a worker that finishes after its lease has expired.
Temporal's workflow documentation provides a production-platform comparison, but this module does not run Temporal or emulate its command history. Whichever runtime you select, preserve the testable distinction between a recorded result and an external action. Export safe transition traces, retain the exact workflow version and evaluate the completed incident report independently. The deployment milestone is a reproducible recovered run, not merely a process that restarts automatically.