Repository navigation
Conversation
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.
Keep a single call inside pytest.raises, as SonarCloud rule S5778 requires: construct the snapshot first, as the test did before.
9 of 11 tasks
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.
doublewhy
marked this pull request as ready for review
October 10, 2026 14:09
2 tasks
doublewhy
added a commit
that referenced
this pull request
Oct 12, 2026
…ter entry Each open entry in the divergence register now names its issue: #1525 to #1557 for the production changes, and #1521 for the smaller corrections. The entries that open pull requests now address move to "Addressed by pull requests" with their issues: GH-7 (#1491, #1490), NS-1 (#1476, #1475), NS-2 (#1480, #1479), MF-2 (#1483, #1482), CL-2 (#1474, #1473), ST-2 (#1472, #1559) and NS-7 and NS-8 (#1488, #1560 and #1561). ST-1 names #1558, GH-3 records that #1415 merged, and "What remains" names #1522 for the unverified and unexamined boundaries.
10 tasks done
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Part of #1344, the fixture-provenance audit (finding ST-2 in the record #1461 proposes). Two tests that claim to cover a failed store commit never reach a commit. Each wraps a store write, runs the real write, then raises
OSError("commit failed")inside the transaction body:test_failed_atomic_control_commit_exposes_no_partial_transition(test_run_310_supervisory_lifecycle.py:865-874on dev) wraps_upsert_record. Since feat(runtime): define control-plane operation lifecycle contract #1235,record_participant_controlclaims a running record (participant_control_mediation.py:112) before it calls_commit_control_transition(:115). The first_upsert_recordcall is now that claim, so the test fails the claim and never reaches the atomic control commit it names.test_local_control_plane_store_rolls_back_snapshot_transaction_failure(test_runtime_control_plane_api.py:1864-1887) wraps_upsert_snapshot. It also savesRuntimeSnapshot()and compares only the snapshot, and an empty snapshot equals the default one, so even a committed write would pass (M3 and M5 below).An error raised in the body takes the
exceptbranch oftransaction()(control_plane_store_local_codec.py:13-24), which callsrollback(). A real commit failure is asqlite3error raised bycommit()in theelsebranch.transaction()re-raises it without a rollback, and_connection()(control_plane_store_local.py:358-363) closes the connection, which discards the open transaction. The audit recorded this on SQLite 3.50.4 with a deferred foreign key:commit()raisedsqlite3.IntegrityError, the transaction stayed open, and nothing persisted afterclose().Both fakes now fail where production fails. A small helper adds a deferred foreign-key violation to the open transaction, in a temporary table local to the connection, so SQLite's own COMMIT raises
sqlite3.IntegrityError: FOREIGN KEY constraint failed. The supervisory fake does this after the control transition's last write, its audit row, so the snapshot, record and audit are all pending when COMMIT fails. No production code changes.Requirement UIDs
Related Issues
Closes #1559
ADR Impact
Changes
implementations/python/tests/store_commit_failure_fixtures.py(new): the helperfail_sqlite_commit(connection). It creates a temporary table with a deferred self-referencing foreign key and inserts a row whose parent does not exist. The module only defines that function, so importing it has no side effects.implementations/python/tests/test_run_310_supervisory_lifecycle.py:test_failed_atomic_control_commit_exposes_no_partial_transitionwraps_insert_auditinstead of_upsert_recordand expectssqlite3.IntegrityError. The running claim is durable when the transition commit fails, and the runtime cannot pair it with a terminal cut (control_plane_durability.py:145-169). So it serves no state until restart, and the test asserts that (get_snapshot()raisesrequires restart) instead of reading an empty history. The restarted store holds no control history, no audit event and one record, the claim, stillRUNNING. The restart requirement is the documented contract:RUNNINGoperation left by an uncertain commit stays claimed for governed recovery or fail-closed handling (docs/decisions/issue-1181-unified-control-plane-mutations-preflight.md:137-140). After the claim and before the terminal commit, one durableRUNNINGrecord remains, and an injected terminal-transaction failure rolls back the whole terminal cut (:342-344).docs/decisions/issue-1092-local-control-plane-durability-preflight.md:262-263)._resynchronize_after_store_errorpoisons the runtime the same way when the reloaded record lacks the terminal cut the commit required (control_plane_durability.py:163-173).test_runtime_poisoned_by_non_terminal_operation_after_uncertain_terminal_commitalready asserts this after a failed provisioning terminal commit (implementations/python/tests/test_issue_1092_control_plane_crash_consistency.py:808). The opposite case, a transition commit that lands and then reports an error, resyncs without poisoning (:2179).implementations/python/tests/test_runtime_control_plane_api.py:test_local_control_plane_store_rolls_back_snapshot_transaction_failurekeeps wrapping_upsert_snapshot, now with the commit failure, and expectssqlite3.IntegrityError. It savesRuntimeSnapshot(metadata={"must": "roll back"})and asserts that the wholeSnapshotState, snapshot and revision, is unchanged.Commits:
21fcf57cchanges both fakes.4d79a194builds that snapshot before thepytest.raisesblock so that only the save runs inside it; SonarCloud reported python:S5778 on21fcf57c, where the constructor call sat inside the block.6058bbe7moves the helper, which both test modules defined identically, into the shared module and imports it from both.Test Plan
On
6058bbe7, fromimplementations/python,uv run --frozen --all-extras python -m pytest tests/test_run_310_supervisory_lifecycle.py tests/test_runtime_control_plane_api.py -q -p no:cacheproviderpassed (104 passed). With-m integrationand with-m fuzz, both modules select no tests (104 deselected each). Local versions: SQLite 3.50.4, CPython 3.14.4.Mutation runs, repeated on
6058bbe7with M3 added (the other rows did not change). A pytest plugin outside the repository patched each broken variant in at runtime; no broken code was written to the package. The old fakes come from dev's copies of the two test modules; the 1092 column istest_startup_recovery_commit_failure_aborts_readiness_and_resumes_without_record_only_writesintest_issue_1092_control_plane_crash_consistency.py, unchanged:transaction()rolls back a failed COMMIT but does not re-raisetransaction()does nothing, so every statement commits on its owntransaction()commits instead of rolling back when the body raisestransaction()drops its rollback when the body raisesrecord_participant_controlreturns an accepted receipt while the store keeps only the running claim, andsave_snapshotreturns normally. Both new tests fail withDID NOT RAISE IntegrityError.assert not restarted.load_snapshot().participant_control_history, because the restarted store holds the history event while the record is still the running claim and no audit exists.assert store.load_snapshot_state() == before(test_runtime_control_plane_api.py:1888): the snapshot write committed before the failing statement, so the store holds revision 1 instead of 0. The supervisory test fails with either fake, and the 1092 recovery-crash test fails too. With the new fake, the transition's snapshot,SUCCEEDEDrecord and audit all persist, soget_snapshot()serves state instead of raisingrequires restart(test_run_310_supervisory_lifecycle.py:885-886)._connection()closes the connection after each operation and SQLite discards the open transaction. This matches the audit's conclusion that atomicity holds on real SQLite because of that close.Mutation plugin (run with
PYTHONPATH=<dir> ST2_MUTATION=M1 ... pytest -p st2_mutations)A trace on dev, with a plugin that logs each store write, shows which call the old supervisory fake interrupts: the call stack at the failing write is
record_participant_control,_claim_record,claim_record,claim_record,fail_record_upsert.On
6058bbe7,nox -s verify-fast-feedback -- --base-rev origin/devpassed: every stage PASS or SKIP, and the two changed test modules ran (104 passed).nox -s lintpassed. Run fromimplementations/python, the pinned toolingruff formatleft the three changed files unchanged andruff checkpassed.make policypassed 15 stages. Requirement governance was skipped because the branch has no requirement UID and no scope file.No package code changes and the PR only edits tests, so package line coverage cannot drop. The CI coverage lane is authoritative.
On
6058bbe7, CI run 38051689522 passed and all 14 required checks pass (28 checks pass, deploy skipped). SonarCloud's quality gate is OK with no new issues.Ground Control Checks
make policyand the test runs are recorded in the Test Plan.Traceability
implementations/python/tests/test_run_310_supervisory_lifecycle.py,implementations/python/tests/test_runtime_control_plane_api.py, with the shared helper inimplementations/python/tests/store_commit_failure_fixtures.pyimplementations/python/packages/raes_runtime/control_plane_store_local_codec.py(transaction),control_plane_store_local.py(_connection,commit_participant_transition),control_plane_store_local_snapshot.py(save_snapshot)docs/requirements/API-404/requirement.md:291) and both test modules as TESTS (:269,:318). RUN-310 traces the supervisory module (docs/requirements/RUN-310/requirement.md:72).Checklist
testdoes not cut a release.