-
Notifications
You must be signed in to change notification settings - Fork 5
Escalation voting improvements #214
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
- simple (default): Early resolution when any option hits quorum (3 votes) - majority: No early resolution; voting stays open until timeout, plurality wins Changes: - Add voting_strategy column to escalations table - Add shouldTriggerEarlyResolution() to check strategy before triggering - Update handlers to set strategy based on escalation level (0=simple, 1+=majority) - Update UI to show strategy-specific status and hide "Require majority" when active - Simplify escalationResolver since both strategies resolve identically on timeout 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Documents Discord API mocking challenges, time-dependent behavior, multi-actor workflows, and proposes solutions including pure logic extraction and time abstraction. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Update test expectations for the new timeout formula: max(0, 36 - 4 * (voteCount - 1)) - 0 votes = 40h (was 24h) - 1 vote = 36h (was 16h) - 2 votes = 32h (was 8h) - 3 votes = 28h (was 0h) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
All these were implicitly testing the same voting logic, but with various layers of indirection in pulling out the output. Not helpful, not worth keeping
Replace dynamic timeout calculation on every poll with a stored scheduled_for timestamp. The column is updated whenever votes change, making the resolver query a simple indexed lookup instead of requiring vote tallies for every pending escalation. - Add migration with backfill for existing pending escalations - Add calculateScheduledFor, updateScheduledFor, getDueEscalations - Update vote/escalate handlers to persist new scheduled time - Simplify resolver to query due escalations directly 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Simplify function signatures by passing the escalation object instead of individual fields. This makes the code cleaner and allows direct use of scheduled_for for Discord timestamps instead of recalculating. - buildVoteMessageContent now takes (modRoleId, escalation, tally, votingStrategy) - buildConfirmedMessageContent now takes (escalation, resolution, tally) - Update all call sites in handlers.ts - Update tests with mock escalation helper 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add getDisabledButtons helper to disable vote buttons on resolution - Handle edge case where user left server or deleted account - Forward resolution notices to mod log channel - Clean up resolution message format with consistent timestamp display 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This pull request enhances the escalation voting system with improved timing, majority voting support, better resolution notifications, and optimized scheduling. The changes introduce a database-backed scheduled resolution time that updates dynamically as votes are cast, and adds a two-tier voting system (simple vs majority).
Key changes:
- Added
scheduled_forcolumn to track computed resolution timestamps, eliminating the need to recalculate on every poll - Extended default voting period from 24hr to 36hr with slower acceleration (4hr per vote instead of 8hr)
- Implemented majority voting strategy that requires timeout before execution (no early resolution)
- Added resolution result messages that forward to mod-log channel
- Disabled voting buttons after resolution while keeping them visible
Reviewed changes
Copilot reviewed 15 out of 16 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| migrations/20251217145416_add_scheduled_for.ts | Adds scheduled_for column to store precomputed resolution timestamps and backfills existing escalations |
| migrations/20251209140659_add_voting_strategy.ts | Adds voting_strategy column to support simple vs majority voting modes |
| app/models/escalationVotes.server.ts | Adds calculateScheduledFor, updateScheduledFor, getDueEscalations, and updateEscalationStrategy functions; updates createEscalation to set initial scheduled time |
| app/helpers/modResponse.ts | Defines voting strategy constants and types |
| app/helpers/escalationVotes.ts | Updates timeout formula from 24 - 8*voteCount to 36 - 4*voteCount for slower acceleration |
| app/helpers/escalationVotes.test.ts | Updates test cases to reflect new timeout formula |
| app/db.d.ts | Adds scheduled_for and voting_strategy columns to Escalations type definition |
| app/commands/escalate/voting.ts | Adds shouldTriggerEarlyResolution function to handle strategy-based early resolution logic |
| app/commands/escalate/strings.ts | Updates message builders to accept Escalation objects, display strategy-specific status messages, and show "Require majority vote" button conditionally |
| app/commands/escalate/strings.test.ts | Adds createMockEscalation helper and updates tests for new function signatures |
| app/commands/escalate/handlers.ts | Updates vote handler to recalculate scheduled_for after votes; implements re-escalation for majority voting; uses new strategy-aware resolution logic |
| app/discord/escalationResolver.ts | Adds getDisabledButtons function; updates resolution to use getDueEscalations, post result messages, forward to mod-log, and handle user-gone scenarios |
| notes/*.md | Documentation for scheduling refactor, voting strategy implementation, and testing challenges |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // Formula: timeout = max(0, 36 - 4 * (voteCount - 1)) | ||
| // 0 votes = 40h, 1 vote = 36h, 2 votes = 32h, 3 votes = 28h |
Copilot
AI
Dec 20, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comment on line 24 describes the formula as max(0, 36 - 4 * (voteCount - 1)) and claims "0 votes = 40h", but the actual implementation uses max(0, 36 - 4 * voteCount) which gives "0 votes = 36h". This comment is incorrect and should be updated to match the implementation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fair but meh
Preview environment removedThe preview for this PR has been cleaned up. |
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
✅ E2E Tests Passed3 passed Tested against: https://214.euno-staging.reactiflux.com |

36hr - (4vote * 4hr)=20hr