# RUN.md: interrupted dual-write demo

Synthetic and local. Python 3.11.15 standard library and SQLite 3.51.0 (bundled). No installs, no network, no real data.

## Order of work

1. `fixtures.json`, `expected.json` and `HYPOTHESIS.md` were written first and hashed into `frozen-hashes.txt` before `dualwrite.py` or `run_scenarios.py` existed.
2. Code written, then run once. All seven scenarios matched on that first run.
3. `test_dualwrite.py` added afterward to check the harness can fail (wrong expectation rejected, crashes were real exits, hot journal present in the S7 control). Adding it also added one recorded-but-unjudged key to S7 output (`journal_present_after_crash`). `expected.json` was not changed.

## Changes after the independent review (2026-10-24)

`expected.json`, `fixtures.json` and `HYPOTHESIS.md` were not changed (hashes verified). Code and tests changed:

- S7 control: the review showed the crash left a journal with a zeroed header, so SQLite never treated it as hot; the uncommitted write stayed in the page cache and never reached the file. A `file_without_journal_has_O-17` check now records this (False). Attempts to force a spill (`PRAGMA cache_size=1`, then 200 x 4 KB padding rows in the crashing transaction) did not spill and were reverted. The test was renamed `test_single_transaction_crash_shows_no_half_write` and the article no longer claims rollback was exercised.
- Reconciler: an intent whose version and status already equal the projection is now marked projected instead of reported as a conflict. Superseded-intent over-flagging and repeat conflict reporting are documented, not fixed.
- New mutant test: replaces `id_audit` with a join-based audit and checks the frozen S2 expectation fails.
- All 7 scenarios still match; 7 of 7 tests pass.

## Commands and output

```
cd docs/drafts/dual-write-reconciliation-2026-10-23/demo
python3 run_scenarios.py
```
See `run-output.txt` (verbatim):
```
S1-normal: PASS  exit_codes=[0]
S2-interrupted-insert: PASS  exit_codes=[137, 0]
S3-interrupted-update: PASS  exit_codes=[137, 0]
S4-reconcile: PASS  exit_codes=[137, 0]
S5-conflict: PASS  exit_codes=[137, 0]
S6-source-unavailable: PASS  exit_codes=[137, 0]
S7-single-transaction-control: PASS  exit_codes=[137, 0]
7 of 7 scenarios matched expected.json
```

```
python3 -m unittest test_dualwrite.py -v
```
See `test-log.txt`: 7 tests, all OK.

```
shasum -a 256 -c <(head -3 frozen-hashes.txt)
```
fixtures.json, expected.json, HYPOTHESIS.md: OK (unchanged since freezing).

## How the crash works

`dualwrite.py write <dir> <writer> <crash_op>` runs as a child process. At the crash point it calls `os._exit(137)`: no exception handlers, no cleanup, no close. The harness then starts a new child process with no crash (the restart) and inspects both stores from the parent process. Exit codes are recorded per scenario.

## Where results live

- `results.json`: full actual output per scenario plus `matches_expected`.
- `work/<scenario>/`: the SQLite files after each run (regenerated each run; not needed for review).
