Posts to the hold desk after the commit - #178
Merged
Merged
Conversation
The hold desk's executor no longer makes its BasicHTTP POST inside the delivery's transaction. It plans the send with the router's processor and inserts a HoldDesk.DeskPost job on this app's Oban, in the new desk_posts queue: the job commits with the step that sent, a delivery that rolls back takes it with it, and it is unique on the send's dedup key, so a redriven step inserts one job. The job makes the POST after the commit, so a slow desk holds no transaction and no SQLite write lock. A POST the desk refuses is retried; once the third has failed, the job delivers error.communication back into the hold through StatifierRouter.Delivery.deliver_event/4 with create: :never over the row from Addresses.by_execution/2, as statifier_router's ADR-0002 Amendment on the outbound BasicHTTP send recommends. A failure that reaches no execution is cancelled and kept as the dead letter. The controller tests drain the queue, a new test shows the desk is called after the delivery returned and outside any transaction, and the refused-desk test reads error.communication from the execution's input log. The guide says why the example performs after the commit. Refs: se-g9t4
DeskPost decoded its instruction with :erlang.binary_to_term/2 and :safe, which refuses an atom the node has not created yet. A job queued before a restart can name one that no module has loaded since (a send's owner kind, a struct field), so its POST attempts raised and the hold ended unreached with no POST made. The job now reads its instruction with :erlang.binary_to_term/1, as the router README's recipe does; the jobs table is written only through this app's repo. A new test writes an instruction naming an atom no code creates and shows the job still posts. Refs: se-g9t4
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.
What changes
The library hold desk's executor (
StatifierExamples.HoldDesk.execute/2) no longer makes its BasicHTTP POST inside the delivery's transaction. It plans the send withStatifierRouter.BasicHTTP.deliver/3and inserts aStatifierExamples.HoldDesk.DeskPostjob on this app's own Oban, in a newdesk_postsqueue. This is the shape statifier_router recommends indocs/adr/0002-addressing.md, the Amendment of 2026-10-02 on a durable execution's outbound BasicHTTP send (the shape ruled by the operator, 2026-10-01).send_id,macrostep,microstep,round,c_index,owner,ordinal, each nil as-), so a redriven step inserts one job.DeskPost.perform/1callsStatifierRouter.BasicHTTP.perform/2outside every delivery, so a slow desk holds no transaction and no SQLite write lock.error.communication, carrying the send's id, throughStatifierRouter.Delivery.deliver_event/4withcreate: :neverover the row fromStatifierRouter.Addresses.by_execution/2, under the plan iddesk_post_failure. The chart still ends indesk_unreached. A failure that reaches no execution (finished hold, missing address row) is cancelled and kept in the jobs table as the dead letter.{:basichttp_send_without_target, send_id}, which statifier_persistence re-enters in the same step. A delayed send is still refused.Every router function the job calls exists in statifier_router 0.9.2 as
mix.lockresolves it:BasicHTTP.deliver/3,BasicHTTP.perform/2,Addresses.by_execution/2,Delivery.deliver_event/4. No dependency moves.Tests
In
test/statifier_examples_web/controllers/basic_http_controller_test.exs, every hold test now drainsdesk_postswithOban.drain_queue/2(the suite runs Oban withtesting: :manual), and the test transport (test/support/desk_transport.ex) can call a hook while the desk answers. New or changed tests::active, one job queued and the desk not yet called. The desk is then called withRepo.in_transaction?()false.error.communication, with its send id, from the execution's input log, because the event now arrives as a delivered external event in a step of its own, not as a same-step re-entry. It still asserts:completedand the 404.Sabotage checks
Each check was run against the controller test file and restored byte-equal:
{:desk_answering, false}not received)error.communicationuniqueoption removed:ok:ok:okwithout inserting the job[:safe]againThe rollback test is therefore not proven to discriminate: a sandboxed suite has one connection. Its test comment says so.
Provenance
:erlang.term_to_binary/1in base64, as the router README's recipe writes them, and reads them back with plain:erlang.binary_to_term/1, as that recipe does. The first commit decoded with:safe, which refuses an atom the node has not created yet: a job queued before a restart can name one that no module has loaded since (a send's owner kind, a struct field), so its POSTs failed and the hold ended unreached with no POST made. The cure commit drops:safe; the jobs table is written only through this app's repo, byDeskPost.new/3. It does not rebuild the POST from url, headers and body:perform/2takes the payload as an opaque term, and the map inside it is a private type in statifier. This is an engineering choice made in this PR.max_attempts: 5).reply_tolocation, until the host prunes it. The guide says so.Gate
mix qualitywas green on each commit's tree: 660 of 660 tests on the first commit, and 661 of 661 on the cure commit, both at 84.7% coverage. The repo gate lock and a machine slot were held from each run through its commit.Review
This PR touches the hold desk's send path and a behaviour a router record cites, so a cold review pass is requested. Its gate tier is the contract tier.