You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
use the same bare, internally generated datamachine_transaction_<integer> identifier for SAVEPOINT, RELEASE SAVEPOINT, and ROLLBACK TO SAVEPOINT
close scopes only after successful commit or rollback boundary closure, preserving retries after rollback/release failures
add executable SQLite persistence/rollback ownership coverage plus deterministic MySQL-shape and rollback failure-path coverage
Root cause and portability
wpdb::prepare( 'RELEASE SAVEPOINT %i', $name ) and the matching rollback query changed the SQL spelling from the bare identifier used by SAVEPOINT to a backtick-quoted identifier. The WordPress SQLite adapter does not preserve the expected boundary under that mixed spelling.
These names are not caller-controlled: TransactionScope constructs them exclusively from the fixed datamachine_transaction_ prefix plus an incrementing integer. Keeping that closed token bare is valid in MySQL and SQLite, avoids adapter-specific identifier quoting, and does not accept raw external identifiers.
Rollback ownership remains fail-closed: RELEASE SAVEPOINT runs only after successful ROLLBACK TO SAVEPOINT; failed rollback, failed release, and failed top-level rollback keep the scope active for retry. A fully closed scope rejects stale commits without issuing SQL.
E2E evidence
Before this change, retained fixture artifact runtime-mt5c9sh5-gcoe1j showed the pending-to-processing update followed by the job remaining pending and the Action Scheduler action completing.
The exact pinned #2578 bundle (b2669b1e59681e58997f2bf6bacfd18eb8bf230d) was rerun against this branch with deterministic OpenAI/GitHub doubles. Artifact runtime-mt5utmim-kzuc6i confirms the next independent SQLite blocker: the ordered SQL is SAVEPOINT datamachine_transaction_1, pending-to-processing UPDATE with empty last_error, then immediate ROLLBACK TO SAVEPOINT datamachine_transaction_1 and RELEASE SAVEPOINT datamachine_transaction_1. There is no release attempt before the rollback, proving this run stops because SQLite's wpdb->update() affected-row result is not exactly 1, not because the portable release spelling fails. The job therefore does not yet advance beyond pending in the full fixture; this evidence is retained rather than hidden.
Verification
php tests/transaction-scope-smoke.php: 0 failures, including rollback failure/retry, release failure/retry, top-level rollback retry, stale-owner rejection, and MySQL-compatible bare SQL shape
focused SQLite TransactionScopeTest: 3 passed, 0 failed (homeboy run 899d51da-0b1a-4947-9411-d1ef7ce57777)
first PR MySQL shard run 32643153590: all four candidate test shard execution steps succeeded; shard 2 failed only afterward at Validate candidate Test shard terminal provenance, an infrastructure/provenance failure rather than a test failure
pre-PR local MySQL-equivalent run 9ffc13d3-ff81-49fe-a34b-7374de040afe: PHPUNIT_ZERO_TESTS cause=recipe_run_payload_unparseable
TransactionScope::rollback() marks the scope inactive before any rollback SQL succeeds, ignores a failed ROLLBACK TO SAVEPOINT, and then unconditionally issues RELEASE SAVEPOINT. If rollback fails but release succeeds, changes intended for rollback can survive while the scope loses its only retry path. The top-level ROLLBACK path has the same premature inactive transition.
Please keep the scope active unless the rollback boundary is successfully closed, issue RELEASE SAVEPOINT only after successful ROLLBACK TO SAVEPOINT, and add failure-path coverage for rollback failure/retry and release failure. Preserve the safe bare internally generated identifier change.
Addressed the blocking rollback-safety finding in 0bc5964. Failed savepoint rollback now skips release and remains retryable; failed release and top-level rollback also preserve retry paths. Deterministic failure-path smoke coverage and focused SQLite integration are green.
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
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
datamachine_transaction_<integer>identifier forSAVEPOINT,RELEASE SAVEPOINT, andROLLBACK TO SAVEPOINTRoot cause and portability
wpdb::prepare( 'RELEASE SAVEPOINT %i', $name )and the matching rollback query changed the SQL spelling from the bare identifier used bySAVEPOINTto a backtick-quoted identifier. The WordPress SQLite adapter does not preserve the expected boundary under that mixed spelling.These names are not caller-controlled:
TransactionScopeconstructs them exclusively from the fixeddatamachine_transaction_prefix plus an incrementing integer. Keeping that closed token bare is valid in MySQL and SQLite, avoids adapter-specific identifier quoting, and does not accept raw external identifiers.Rollback ownership remains fail-closed:
RELEASE SAVEPOINTruns only after successfulROLLBACK TO SAVEPOINT; failed rollback, failed release, and failed top-level rollback keep the scope active for retry. A fully closed scope rejects stale commits without issuing SQL.E2E evidence
Before this change, retained fixture artifact
runtime-mt5c9sh5-gcoe1jshowed the pending-to-processing update followed by the job remainingpendingand the Action Scheduler action completing.The exact pinned #2578 bundle (
b2669b1e59681e58997f2bf6bacfd18eb8bf230d) was rerun against this branch with deterministic OpenAI/GitHub doubles. Artifactruntime-mt5utmim-kzuc6iconfirms the next independent SQLite blocker: the ordered SQL isSAVEPOINT datamachine_transaction_1, pending-to-processingUPDATEwith emptylast_error, then immediateROLLBACK TO SAVEPOINT datamachine_transaction_1andRELEASE SAVEPOINT datamachine_transaction_1. There is no release attempt before the rollback, proving this run stops because SQLite'swpdb->update()affected-row result is not exactly1, not because the portable release spelling fails. The job therefore does not yet advance beyond pending in the full fixture; this evidence is retained rather than hidden.Verification
php tests/transaction-scope-smoke.php: 0 failures, including rollback failure/retry, release failure/retry, top-level rollback retry, stale-owner rejection, and MySQL-compatible bare SQL shapeTransactionScopeTest: 3 passed, 0 failed (homeboyrun899d51da-0b1a-4947-9411-d1ef7ce57777)homeboyrun25ea0549-14b7-44db-b49f-61734c367510)git diff --check: passed32643153590: all four candidate test shard execution steps succeeded; shard 2 failed only afterward atValidate candidate Test shard terminal provenance, an infrastructure/provenance failure rather than a test failure9ffc13d3-ff81-49fe-a34b-7374de040afe:PHPUNIT_ZERO_TESTS cause=recipe_run_payload_unparseableFixes #3340
Refs #2578
Parent #3113