fix: preserve post-commit errors in v0.14.6 - #62
Merged
Conversation
Track SQL commitment before retry and failure handling so Rails callback errors cannot relabel a completed turn or mask its original exception. Carry the committed phase through coordination handlers and restore the original error at synchronous caller and worker boundaries. Cover real transaction callbacks, rollback, domain rejection, busy and deadline failures, waiting callers, and subsequent execution with strict red/green and implementation-reversal evidence. Document the error boundary and bump the version and generated lockfile to 0.14.6.
Greptile SummaryThis PR distinguishes exceptions raised after an Active Record transaction has durably committed from ordinary pre-commit failures.
Confidence Score: 5/5The PR appears safe to merge, with no concrete correctness, security, or repository-rule violations identified. The transaction-state checks consistently separate rolled-back work from errors raised after durable commitment, public boundaries preserve the original exception, and the regression tests cover the principal failure and cleanup paths. Important Files Changed
Reviews (1): Last reviewed commit: "fix: preserve committed turns in v0.14.6" | Re-trigger Greptile |
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.
An Active Record
after_commitcallback registered through a commit action could raise after application rows, actor state, and message completion had committed. The executor then restored its old snapshot and tried failure finalization against the deleted claim, replacing the original error withLostActivation: message claim changed.Track actual SQL commitment before deadline translation and SQLite retries. An internal
CommittedTransactionErrorcarries the committed phase through executor and coordination rescue handlers; synchronous caller assistance and workers re-raise the original exception object with its backtrace and cause. Preserve completed state/results, prevent false retries/rejections/dead letters, and let subsequent messages progress. Callback-raisedRejected,LostActivation, record-lookup, deadline, and busy errors follow the same boundary. Pre-commit rollback, retry, and domain rejection remain unchanged.Bump the gem to 0.14.6, date the changelog, and regenerate
Gemfile.lockand inline RBS signatures. Update the correctness and architecture documentation. No migration, Statesman dependency, authorization change, or new delivery API is required. This is an Active Record-specific surface; native JS commit actions have no Rails after-commit callback registration API.Error-delivery boundary
A separate waiting caller may observe the durable result before a worker callback raises. Callback errors are exposed in the executing process; completed history is not rewritten and failure cannot be delivered retroactively. The callback itself is not durable work and Rails may skip later callbacks after an exception. Use an idempotent effect when callback work needs independent retries.
TDD evidence
The first independent core regression used a real Active Record transaction and model callback registered through a commit action. Before implementation it verified committed application/actor/message state, then failed with:
Further red tests exposed rejection finalization, worker lease-error handling, and SQLite deadline/busy retries after commit. Temporarily reversing only the six implementation files produced 12 runs, 69 assertions, 8 failures, 0 errors, 0 skips, including the original missing-claim failure. Restoring those files byte-for-byte produced 12 runs, 125 assertions, 0 failures/errors/skips.
The regression suite covers successful callbacks, failure in
before_commitafter the fenced block finishes, rollback/retry/rejection, original exception identity/backtrace/cause, completed rows, no duplicate execution or false dead letters, activation cleanup, subsequent execution, and a real worker on a separate connection with a waiting caller. Concurrency uses queue barriers, not sleeps.Validation
Ruby 3.3.9, Active Record 8.1.3.1. The full
bundle exec rakepipeline passed after the version bump: Minitest, Standard, RuboCop, RBS generation/validation, Steep, and Brakeman (zero security warnings).The full adapter matrix below passed against the same implementation before the metadata-only version bump. Every database was a dedicated disposable fixture; both containers were removed afterward.
bundle exec rake(SQLite)SOLID_OBJECTS_DATABASE_URL='postgresql://postgres@127.0.0.1:55449/solid_objects_post_commit?pool=20' bundle exec rake test TESTOPTS=--verboseSOLID_OBJECTS_DATABASE_URL='mysql2://root@127.0.0.1:53319/solid_objects_post_commit?pool=20' bundle exec rake test TESTOPTS=--verboseSOLID_OBJECTS_DATABASE_URL='trilogy://root@127.0.0.1:53319/solid_objects_post_commit_trilogy?pool=20' bundle exec rake test TESTOPTS=--verbosePostgreSQL was 18.4; MySQL was 8.4.11. Skips are existing adapter-specific tests; none of the 12 new cases skipped on any adapter. The focused regression suite also passed with Rails 7.1.6.
The separate Statesman prototype checkout was updated to expect the original
RuntimeError, "failed after_commit", preserving all persistence and subsequent-transition assertions. Its SQLite/PostgreSQL/MySQL matrix passed 28 tests, 136 assertions per adapter, no failures/errors/skips, and its full SQLite lint/type pipeline passed. Those sibling-repository changes are outside this Ruby PR.