Skip to content

Wish voting across forges and Discord

Recommendation (13 September 2026): define a Praetor ballot and vote ledger first, then treat GitHub, Gitea, GitLab, and Discord as projections and event sources. A platform reaction or upvote is evidence of engagement, not automatically a qualified ballot. Keep platform IDs, actor namespaces, ballot revisions, removals, and backfill cursors durable. Start with one read-only importer and an explicit human review projection; defer bot posting and live voting until replay and identity policy are proven.

What the platforms actually expose

GitHub Discussions have native polls in the product. The current GraphQL Discussions reference exposes DiscussionPollOption.totalVoteCount and viewer-specific viewerHasVoted, plus mutations for voting and removing a vote. It also exposes discussion and top-level-comment upvotes. GitHub’s participation guide confirms polls and upvotes in the UI. These are GraphQL capabilities, not REST endpoints: the REST reactions API supports emoji reactions on discussions and issue-like resources, but an emoji is not a native poll choice and its counts have different semantics. Any implementation must pin and record the GraphQL schema/document revision; no assumption of a REST “create poll” endpoint is justified by the current docs.

Gitea’s current API is documented as version 1.27.3/OpenAPI 3.0.3 (API index). Its issue API provides create/update/comment/reaction operations, including paginated issue reactions and add/remove reaction endpoints (list, add, remove). No native poll or qualified-vote contract was verified. Forgejo/Gitea reactions may be a useful public projection, but a reaction is only a platform-specific signal that must be mapped to a Praetor ballot policy.

GitLab’s current emoji reactions API calls reactions “awardables” and supports issues, merge requests, snippets, epics, and comments. The issues API supports filtering by the authenticated user’s reaction, but the reviewed API does not establish a native issue poll with ballot choices, voter lists, or immutable close semantics. Treat GitLab reaction counts as unqualified signals unless a later, version-pinned API check proves a stronger feature.

Discord has a native message poll. The Poll Resource limits creation to 10 answers, question text to 300 characters, answer text to 55, and duration to at most 32 days. Poll results may be absent while running and can be temporarily inaccurate; after expiry, is_finalized indicates the background precise tally. The endpoint GET /channels/{channel.id}/polls/{message.id}/answers/{answer_id} returns voters with after and limit 1–100, so complete backfill requires a durable per-answer cursor and deduplication. Apps cannot vote on polls, and creation is through Create Message; a bot cannot qualify its own vote as an end-user ballot.

Discord emits add/remove poll-vote Gateway events (Gateway Events), one event per answer for multi-select polls. A reconnectable consumer must persist event identity and sequence/resume state; a Gateway event is not a durable ledger by itself. Gateway sends are limited to 120 events per connection per 60 seconds (Gateway), while HTTP API rate limits and retry headers require normal backoff handling. HTTP interactions require Ed25519 signature and timestamp validation and an initial response within three seconds (Interactions, receiving/responding). These constraints make a bot a stateful worker with leases, replay, backfill, and rate-limit handling, not merely a connection wrapper.

The repository currently has only gopkg.in/yaml.v3 in go.mod; neither Discord Go library is an existing dependency. Upstream DiscordGo v0.29.0 lists polls among its new features and is a lower-level binding. DisGo advertises full REST, Gateway, interactions, components, and rate-limit coverage, with RPC listed as missing; its organization page shows continuing 2026 activity. These claims are library-maintainer claims, not Praetor validation. Do not install either during design. If Discord is selected later, compare pinned commits against the exact poll, voter-pagination, Gateway resume, and interaction APIs needed; otherwise a small direct REST/Gateway adapter may be easier to audit.

Canonical model and workflow

Use an immutable or versioned WishBallot: stable internal wish ID, ballot revision, ordered options, eligibility and close policy, source policy, and digest. Store Vote records keyed by (ballot revision, actor namespace, actor ID, option) with observed/removed timestamps, source event or page cursor, and receipt hash. Store platform bindings separately: provider, installation/account, repository or guild/channel, message/discussion/issue ID, and option mapping. Never use a login string without its namespace and installation context. Account linking must be explicit and revocable; anonymous or inaccessible actors remain counted only in provider totals.

Counts must distinguish native totals, imported voter identities, deduplicated qualified votes, removals, and unknown/incomplete pages. A Discord is_finalized result is authoritative for the provider’s count but does not prove every voter was imported. GitHub’s viewerHasVoted is viewer-relative, not a voter roster. A Gitea/GitLab emoji count is not a qualified ballot. Reconciliation should periodically backfill pages, ingest removals, detect changed poll definitions, and preserve prior observations instead of overwriting history.

End users should submit wishes through a Praetor-owned form or command with moderation and rate limits. Public platforms can receive a reviewed projection containing the canonical ballot revision and stable option labels. Private ballots may project only aggregate status. Do not publish actor identity or private-source content without the configured policy. Moderators need an auditable accept/reject/merge action; rejected or ambiguous submissions remain recorded but excluded from candidate ranking.

Implement first: canonical schema, provider binding, durable observation/replay records, one GitHub GraphQL read path and one Discord read/backfill path behind feature flags, then fixture-based removal and changed-definition tests. Implement later: Gitea/GitLab reaction projections, Discord bot interactions, public posting, and multi-platform deduplication. No live platform outcome is claimed by this research, and no public post or authentication was performed.

Sources were checked against upstream documentation or repositories on 2026-09-13. Moving schemas and library branches must be pinned by commit or release in any implementation receipt.