From 21fcf57c26ac2f8d16a87c4b531a2dc1eba2c038 Mon Sep 17 00:00:00 2001 From: Yernat Yestekov <2068106+doublewhy@users.noreply.github.com> Date: Sat, 10 Oct 2026 03:03:08 -0700 Subject: [PATCH 1/3] test(runtime): make the store atomicity fakes fail at sqlite's commit Both fakes raised OSError inside the transaction body after a real upsert, so they exercised the rollback branch of transaction(). A real commit failure is a sqlite3 error raised by commit() itself, which transaction() re-raises without a rollback; the per-operation connection close then discards the open transaction. The supervisory fake also failed the running claim that #1235 put ahead of the control transition, so it never reached the atomic commit it names. Add a deferred foreign-key violation to the transaction so SQLite's own COMMIT fails with sqlite3.IntegrityError: after the control transition's last write in the supervisory test, and after the snapshot upsert in the store test. The store test now saves a non-empty snapshot and compares the whole snapshot state, because an empty snapshot equals the default and hid a committed write. --- .../test_run_310_supervisory_lifecycle.py | 37 +++++++++++++------ .../tests/test_runtime_control_plane_api.py | 29 +++++++++++---- 2 files changed, 47 insertions(+), 19 deletions(-) diff --git a/implementations/python/tests/test_run_310_supervisory_lifecycle.py b/implementations/python/tests/test_run_310_supervisory_lifecycle.py index f1bdaba65..5e8544740 100644 --- a/implementations/python/tests/test_run_310_supervisory_lifecycle.py +++ b/implementations/python/tests/test_run_310_supervisory_lifecycle.py @@ -844,6 +844,20 @@ def test_unresolved_typed_target_appends_a_bounded_rejection_without_fallback() assert event["predecessor_event_refs"] == [] +def _fail_sqlite_commit(connection: sqlite3.Connection) -> None: + """Make SQLite's own COMMIT of the open transaction fail, as a real commit-time failure does. + + SQLite checks a deferred foreign key at COMMIT, so commit() raises sqlite3.IntegrityError and the transaction + stays open (fixture provenance audit #1344, finding ST-2). The temporary table exists only on this connection. + """ + + connection.execute( + "CREATE TEMP TABLE commit_failure " + "(id INTEGER PRIMARY KEY, parent INTEGER REFERENCES commit_failure (id) DEFERRABLE INITIALLY DEFERRED)" + ) + connection.execute("INSERT INTO commit_failure (id, parent) VALUES (1, 2)") + + def test_failed_atomic_control_commit_exposes_no_partial_transition( tmp_path: Path, monkeypatch: pytest.MonkeyPatch, @@ -862,18 +876,17 @@ def test_failed_atomic_control_commit_exposes_no_partial_transition( payload_ref="payload:proposal-1", ) - real_upsert = store._upsert_record + real_insert_audit = store._insert_audit - def fail_record_upsert( - connection: sqlite3.Connection, - record: ControlPlaneOperationRecord, - ) -> None: - real_upsert(connection, record) - raise OSError("commit failed") + def fail_transition_commit(connection: sqlite3.Connection, event: AuditEvent) -> None: + # The idempotency claim commits first, in its own transaction. The audit row is the control transition's + # last write, so its snapshot, record and audit are all pending when COMMIT fails. + real_insert_audit(connection, event) + _fail_sqlite_commit(connection) - monkeypatch.setattr(store, "_upsert_record", fail_record_upsert) + monkeypatch.setattr(store, "_insert_audit", fail_transition_commit) identity = _identity() - with pytest.raises(OSError, match="commit failed"): + with pytest.raises(sqlite3.IntegrityError, match="FOREIGN KEY constraint failed"): control_plane.record_participant_control( _PARTICIPANT, intent, @@ -881,12 +894,14 @@ def fail_record_upsert( idempotency_key="key-1", ) - assert not control_plane.snapshot.participant_control_history + # The durable claim is still running with no terminal cut, so the runtime serves no state until restart. + with pytest.raises(RuntimeError, match="requires restart"): + control_plane.get_snapshot() control_plane.close() restarted = LocalControlPlaneStore(tmp_path / "control-plane") restarted.admit_runtime(target_scope="target:stub", run_scope="run:default") assert not restarted.load_snapshot().participant_control_history - assert not restarted.load_records() + assert [record.status.state for record in restarted.load_records().values()] == [OperationState.RUNNING] assert not restarted.read_audit() diff --git a/implementations/python/tests/test_runtime_control_plane_api.py b/implementations/python/tests/test_runtime_control_plane_api.py index 4c983b714..99e0df946 100644 --- a/implementations/python/tests/test_runtime_control_plane_api.py +++ b/implementations/python/tests/test_runtime_control_plane_api.py @@ -1861,30 +1861,43 @@ def test_local_control_plane_store_commits_snapshot_to_wal_database(tmp_path: Pa assert integrity == ("ok",) +def _fail_sqlite_commit(connection: sqlite3.Connection) -> None: + """Make SQLite's own COMMIT of the open transaction fail, as a real commit-time failure does. + + SQLite checks a deferred foreign key at COMMIT, so commit() raises sqlite3.IntegrityError and the transaction + stays open (fixture provenance audit #1344, finding ST-2). The temporary table exists only on this connection. + """ + + connection.execute( + "CREATE TEMP TABLE commit_failure " + "(id INTEGER PRIMARY KEY, parent INTEGER REFERENCES commit_failure (id) DEFERRABLE INITIALLY DEFERRED)" + ) + connection.execute("INSERT INTO commit_failure (id, parent) VALUES (1, 2)") + + def test_local_control_plane_store_rolls_back_snapshot_transaction_failure( tmp_path: Path, monkeypatch: pytest.MonkeyPatch, ): store = _admitted_local_store(tmp_path / "cp-store") + before = store.load_snapshot_state() real_upsert = store._upsert_snapshot - def fail_upsert( + def fail_commit_after_upsert( connection: sqlite3.Connection, snapshot: RuntimeSnapshot, *, revision: int = 0, ) -> None: real_upsert(connection, snapshot, revision=revision) - raise OSError("commit failed") + _fail_sqlite_commit(connection) - monkeypatch.setattr(store, "_upsert_snapshot", fail_upsert) - snapshot = RuntimeSnapshot() - revision = store.load_snapshot_state().revision + monkeypatch.setattr(store, "_upsert_snapshot", fail_commit_after_upsert) - with pytest.raises(OSError, match="commit failed"): - store.save_snapshot(snapshot, expected_revision=revision) + with pytest.raises(sqlite3.IntegrityError, match="FOREIGN KEY constraint failed"): + store.save_snapshot(RuntimeSnapshot(metadata={"must": "roll back"}), expected_revision=before.revision) - assert store.load_snapshot() == RuntimeSnapshot() + assert store.load_snapshot_state() == before def test_local_control_plane_store_preserves_concurrent_operation_writes(tmp_path: Path) -> None: From 4d79a194b2b7603fcb384b4887123296e23130d6 Mon Sep 17 00:00:00 2001 From: Yernat Yestekov <2068106+doublewhy@users.noreply.github.com> Date: Sat, 10 Oct 2026 03:32:24 -0700 Subject: [PATCH 2/3] test(runtime): build the snapshot before the raising save Keep a single call inside pytest.raises, as SonarCloud rule S5778 requires: construct the snapshot first, as the test did before. --- implementations/python/tests/test_runtime_control_plane_api.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/implementations/python/tests/test_runtime_control_plane_api.py b/implementations/python/tests/test_runtime_control_plane_api.py index 99e0df946..5334e4cc3 100644 --- a/implementations/python/tests/test_runtime_control_plane_api.py +++ b/implementations/python/tests/test_runtime_control_plane_api.py @@ -1893,9 +1893,10 @@ def fail_commit_after_upsert( _fail_sqlite_commit(connection) monkeypatch.setattr(store, "_upsert_snapshot", fail_commit_after_upsert) + snapshot = RuntimeSnapshot(metadata={"must": "roll back"}) with pytest.raises(sqlite3.IntegrityError, match="FOREIGN KEY constraint failed"): - store.save_snapshot(RuntimeSnapshot(metadata={"must": "roll back"}), expected_revision=before.revision) + store.save_snapshot(snapshot, expected_revision=before.revision) assert store.load_snapshot_state() == before From 6058bbe7404d830a9ec97696e35f133ce4feec30 Mon Sep 17 00:00:00 2001 From: Yernat Yestekov <2068106+doublewhy@users.noreply.github.com> Date: Sat, 10 Oct 2026 05:19:35 -0700 Subject: [PATCH 3/3] test(runtime): share the sqlite commit-failure helper Both store atomicity tests defined the same fake. Move it to tests/store_commit_failure_fixtures.py, a support module with no import side effects, and import it from both modules. --- .../tests/store_commit_failure_fixtures.py | 19 +++++++++++++++++++ .../test_run_310_supervisory_lifecycle.py | 17 ++--------------- .../tests/test_runtime_control_plane_api.py | 17 ++--------------- 3 files changed, 23 insertions(+), 30 deletions(-) create mode 100644 implementations/python/tests/store_commit_failure_fixtures.py diff --git a/implementations/python/tests/store_commit_failure_fixtures.py b/implementations/python/tests/store_commit_failure_fixtures.py new file mode 100644 index 000000000..55211c127 --- /dev/null +++ b/implementations/python/tests/store_commit_failure_fixtures.py @@ -0,0 +1,19 @@ +"""Shared fake for a local control-plane store write whose SQLite COMMIT fails.""" + +from __future__ import annotations + +import sqlite3 + + +def fail_sqlite_commit(connection: sqlite3.Connection) -> None: + """Make SQLite's own COMMIT of the open transaction fail, as a real commit-time failure does. + + SQLite checks a deferred foreign key at COMMIT, so commit() raises sqlite3.IntegrityError and the transaction + stays open (fixture provenance audit #1344, finding ST-2). The temporary table exists only on this connection. + """ + + connection.execute( + "CREATE TEMP TABLE commit_failure " + "(id INTEGER PRIMARY KEY, parent INTEGER REFERENCES commit_failure (id) DEFERRABLE INITIALLY DEFERRED)" + ) + connection.execute("INSERT INTO commit_failure (id, parent) VALUES (1, 2)") diff --git a/implementations/python/tests/test_run_310_supervisory_lifecycle.py b/implementations/python/tests/test_run_310_supervisory_lifecycle.py index 5e8544740..520e4a76c 100644 --- a/implementations/python/tests/test_run_310_supervisory_lifecycle.py +++ b/implementations/python/tests/test_run_310_supervisory_lifecycle.py @@ -53,6 +53,7 @@ participant_runtime_state_contract_diagnostics, ) from starlette.testclient import TestClient +from store_commit_failure_fixtures import fail_sqlite_commit pytestmark = pytest.mark.control_plane_conformance @@ -844,20 +845,6 @@ def test_unresolved_typed_target_appends_a_bounded_rejection_without_fallback() assert event["predecessor_event_refs"] == [] -def _fail_sqlite_commit(connection: sqlite3.Connection) -> None: - """Make SQLite's own COMMIT of the open transaction fail, as a real commit-time failure does. - - SQLite checks a deferred foreign key at COMMIT, so commit() raises sqlite3.IntegrityError and the transaction - stays open (fixture provenance audit #1344, finding ST-2). The temporary table exists only on this connection. - """ - - connection.execute( - "CREATE TEMP TABLE commit_failure " - "(id INTEGER PRIMARY KEY, parent INTEGER REFERENCES commit_failure (id) DEFERRABLE INITIALLY DEFERRED)" - ) - connection.execute("INSERT INTO commit_failure (id, parent) VALUES (1, 2)") - - def test_failed_atomic_control_commit_exposes_no_partial_transition( tmp_path: Path, monkeypatch: pytest.MonkeyPatch, @@ -882,7 +869,7 @@ def fail_transition_commit(connection: sqlite3.Connection, event: AuditEvent) -> # The idempotency claim commits first, in its own transaction. The audit row is the control transition's # last write, so its snapshot, record and audit are all pending when COMMIT fails. real_insert_audit(connection, event) - _fail_sqlite_commit(connection) + fail_sqlite_commit(connection) monkeypatch.setattr(store, "_insert_audit", fail_transition_commit) identity = _identity() diff --git a/implementations/python/tests/test_runtime_control_plane_api.py b/implementations/python/tests/test_runtime_control_plane_api.py index 5334e4cc3..97b7c17b7 100644 --- a/implementations/python/tests/test_runtime_control_plane_api.py +++ b/implementations/python/tests/test_runtime_control_plane_api.py @@ -66,6 +66,7 @@ ) from starlette.requests import Request from starlette.testclient import TestClient +from store_commit_failure_fixtures import fail_sqlite_commit pytestmark = pytest.mark.control_plane_conformance @@ -1861,20 +1862,6 @@ def test_local_control_plane_store_commits_snapshot_to_wal_database(tmp_path: Pa assert integrity == ("ok",) -def _fail_sqlite_commit(connection: sqlite3.Connection) -> None: - """Make SQLite's own COMMIT of the open transaction fail, as a real commit-time failure does. - - SQLite checks a deferred foreign key at COMMIT, so commit() raises sqlite3.IntegrityError and the transaction - stays open (fixture provenance audit #1344, finding ST-2). The temporary table exists only on this connection. - """ - - connection.execute( - "CREATE TEMP TABLE commit_failure " - "(id INTEGER PRIMARY KEY, parent INTEGER REFERENCES commit_failure (id) DEFERRABLE INITIALLY DEFERRED)" - ) - connection.execute("INSERT INTO commit_failure (id, parent) VALUES (1, 2)") - - def test_local_control_plane_store_rolls_back_snapshot_transaction_failure( tmp_path: Path, monkeypatch: pytest.MonkeyPatch, @@ -1890,7 +1877,7 @@ def fail_commit_after_upsert( revision: int = 0, ) -> None: real_upsert(connection, snapshot, revision=revision) - _fail_sqlite_commit(connection) + fail_sqlite_commit(connection) monkeypatch.setattr(store, "_upsert_snapshot", fail_commit_after_upsert) snapshot = RuntimeSnapshot(metadata={"must": "roll back"})