04 — Make engine-owned callbacks silent until committed delivery
## Objective Rolled-back/rejected engine operations and diagnostic previews must emit no external effects, including through model callbacks outside controllers. ## Existing paths and evidence app/models/{poll,proposal,case,point,speaker}.rb; app/services/voting_engine/{command_processor,bias_poll_lifecycle,case_lifecycle,operations_correction}.rb; app/services/bias/{formal_proposal_operation,formal_proposal_registrar}.rb; app/controllers/secretary/polls_controller.rb. Poll has create/update/destroy broadcasts. BiasPollLifecycle#withdraw calls Proposal#destroy_speaker, which broadcasts immediately. Proposal creation can update case locks and create speakers; Speaker has immediate broadcasts and after_commit router queue work. Formal registration encloses the engine command in an outer meeting transaction. OperationsCorrection#preview deliberately rolls back a savepoint. ## Changes - Separate transactional domain persistence from external publication for all callbacks reached by engine-owned work. Cover poll preparation before start/unanimous commands, rejected poll cleanup, bias registration/auto-generated case-start proposals, withdrawal, cascade poll/proposal deletion, speaker creation/removal and router queue publication. - Prefer explicit ownership and shared extracted methods. If necessary introduce a proposed narrow VotingEngine effect context: nesting-safe, thread/fiber-isolated, restored in ensure; never global skip_callback or process-wide suppression. - Establish effect ownership when callbacks are registered. Checking only temporary context inside after_commit fails when that context has already exited. - Keep speaker records, counters, locks, attendance, encryption and other canonical writes in their existing transaction; consumers must not redo domain lifecycle mutations. - Poll preparation remains externally silent until an accepted command creates an event. Preserve rejected receipts and existing validation/cleanup behavior rather than accidentally rolling away audit records. - Preserve existing behavior of unrelated non-engine editing/deletion. Feed affected IDs/deletion context to the effect plan rather than parallel broadcast implementations. ## Acceptance and tests Extend spec/services/voting_engine/{application,bias_poll_lifecycle,operations_correction}_spec.rb and relevant poll/proposal controller/model specs. Spy on Turbo, jobs, mail and integration clients: zero effects before outer commit, after conflict, on outbox persistence failure, after outer rollback and preview rollback. Include speaker and cascade-destroy paths. Existing bias withdrawal spec asserts an immediate broadcast: replace with mutation-only assertions and move publication expectation to consumer coverage. Ordinary non-engine operations must still refresh. Run bundle exec rspec spec/services/voting_engine/application_spec.rb spec/services/voting_engine/bias_poll_lifecycle_spec.rb spec/services/voting_engine/operations_correction_spec.rb spec/controllers/secretary/polls_controller_spec.rb spec/controllers/proposals_controller_spec.rb; RuboCop changed Ruby. ## Depends on https://task.matda.me/tasks/tsk_2qravp6n9b — capture/ownership contract. https://task.matda.me/tasks/tsk_5t6us3yxjk — coordinate durable context persistence. Do not enable suppression in production before matching consumers are ready. ## Questions None currently blocking. Callback ownership should be solved from code and tests, not by globally changing all application callbacks to after_commit.
Checklist
0 of 0 completeNo checklist items.
- Area
- Kaukus
- Kind
- Chore
- Priority
- Medium
- Task ID
tsk_5fdrm4pptn- Updated