Finish outcome corrections and non-voting meeting edge cases
Implement the chair-selected-option tie-break and support streaming-only meetings without voting seats. Preserve exact-resource authorization, audit, confirmation, and active-poll safeguards. Scope decision Voting eligibility is immutable while a vote is open. Secretary roster add/remove/correction commands are not part of this work and must not be implemented as mid-vote synchronization. The electorate is captured once when voting starts; eligibility changes wait until the run has ended. PR #4183 structural review (head 3c6524f1a3d4df98ded6bbec76b8905e443c8ad7): two confirmed P1 issues must be resolved before production cutover. 1. Outcome corrections can invalidate canonical participation. Reproduced with a temporary regression spec: approve a bias decision using a casting vote, close the poll, then reset the casting vote. The reset is accepted and the poll verdict becomes pending, but the approved overlay and replacement deputy remain active. VoteCommands#change_casting_vote permits completed polls without reconciling the applied bias decision. Chair-selected outcomes also remain direct updates in Secretary::PollsController#update, outside the command boundary. Route all outcome corrections through a shared lifecycle operation that either updates dependent participation atomically or rejects corrections requiring an explicit reset. Cover casting-vote application/reset and chair-selected outcomes, dependent participation state, audit, and active-poll safeguards. Relevant: app/services/voting_engine/vote_commands.rb:56; app/services/voting_engine/bias_poll_lifecycle.rb:144; app/controllers/secretary/polls_controller.rb:92. 2. Non-voting meetings cannot start. Reproduced: Application start_meeting with no councillor attendance returns invalid_roster. All starts initialize a voting run, including Cloud Router callbacks and EndMeetingsJob, so streaming-only meetings cannot complete the lifecycle. Explicitly support meetings without a voting electorate, without fabricating participants or adding legacy fallback. Cover manual and Cloud Router start/end, automatic closure, reset/restart, and empty-electorate presentation. Relevant: app/services/voting_engine/transitions.rb:159; app/services/voting_engine/application.rb; app/controllers/api/cloud_router_callbacks_controller.rb; app/jobs/end_meetings_job.rb. Superseded: this task targets the scrapped canonical Rails voting engine and its chair-selected outcome model. The active relational Rust voting runtime deliberately uses casting weight rather than arbitrary chair-selected outcomes, and meeting startup no longer initializes that Rails voting run. Any still-relevant meeting lifecycle safeguards belong in the Rust boundary production-readiness work.
Checklist
0 of 4 complete- Unify casting-vote and chair-selected outcome corrections under the canonical lifecycle; reconcile dependent bias participation atomically or reject corrections requiring reset. Regress close approved bias poll → reset deciding casting vote → unresolved result with deputy incorrectly active.
- Support streaming-only meetings with no voting seats: regress invalid_roster on start and cover manual/Cloud Router start/end, automatic closure, reset/restart, and empty-roster presentation without fabricated participants.
- Add confirmation, authorization, audit, and active-poll safeguards
- Add controller and edge-case regression coverage
- Area
- Kaukus
- Kind
- Feature
- Priority
- High
- Task ID
tsk_zxbg4uyua2- Updated