Testing only · not a live election
Each chapter has a repeatable test code — vote as many times as you like; test votes are never recorded or counted.
At-LargeTEST-ATLARGE-2026-DEMO
ChicagoTEST-CHI-2026-DEMO
Metro DCTEST-DC-2026-DEMO0
Los AngelesTEST-LA-2026-DEMO0
New York CityTEST-NYC-2026-DEMO
Demo SandboxTEST-SANDBOX-2026-DEMO
NPC At-Large Election 2025TEST-NPC-ATLARGE-2025
NPC Co-Chair Election 2025TEST-NPC-COCHAIR-2025
Voting members: use the link and code you were sent. Chapter ballots on this service:
Election administration
Build elections, import voter rolls, watch turnout, adjudicate provisionals, void & reissue, and view results — all in the browser console.
Open the Admin Console →Developers: RosaVote is API-first — see the API reference · voting methods · tabulation accuracy & tests · Why RosaVote.
Just exploring? The console’s sign-in page has a one-tap demo sign-in that opens as national root admin on a synthetic-data sandbox — the full admin experience, no credentials needed.
How to get access
The console needs an admin token (entered on its sign-in
screen; nothing here works without one). The chapter TEST codes at
the top of this page are voting codes, not admin tokens —
they open ballots, never the console. National (root) token:
held in Secret Manager as ballot-admin-token in the
dsa-org-tools project — staff with project access
retrieve it via
gcloud secrets versions access latest --secret ballot-admin-token.
Chapter tokens: minted by national staff
(tools/seed_config.py admin --role chapter) and scoped so a
chapter's admins see only their own poll. Tokens are stored hashed and
can't be recovered — keep them in a password manager, and every
admin action is recorded in the audit log under the token's name.
How to test without affecting anything
Use the repeatable TEST codes listed at the top of this page — they walk the full voting experience (ballot, review, receipt) but never write a ballot, so test votes can't pollute results or turnout. Cast as many as you like. To rehearse the admin side, create a throwaway poll with the console's election builder, import a few fake member IDs on the Voters tab, vote with the minted codes, close the poll, and watch results unlock — then leave the test poll closed or ask national to archive it.
What's on each ballot
Every chapter ballot has three sections on one page: Section 1 · Chapter Poll — five shared questions polled identically in every chapter (endorsement, campaign structure, a ranked test field, pledges, open comment), reported per chapter and aggregated; Section 2 · Convention Delegates — the chapter's delegate election by secret ballot, ranked choice; and Section 3 · Local Issues — two ballot issues unique to that chapter.
Delegate election timing (Const. Art. V §5)
Unlike chapter polls and local issues — which chapters may schedule
freely — the delegate election is constitutionally time-boxed: it may
be held no more than four months and no less than forty-five days before
the National Convention opens, and only after delegates have been
apportioned (apportionment uses membership as of four months prior). Art. V
§5 also requires the delegate election be by secret ballot (enforced
here by construction) and permits proportional representation within the Bylaws
— cite Scottish STV and the expanded-count alternates rule in the chapter's
adopted election rules. Practical implication: because this ballot combines
all three sections on one page, the whole ballot inherits the delegate window
— set each chapter's opens_at/closes_at inside it,
or run the poll/local sections as a separate ballot if a chapter wants them
outside that window.
Technical setup
A small Flask service on Cloud Run (auto-scaling, scales to zero) with Firestore for codes and ballots and BigQuery as the membership source. Each chapter is its own poll (/p/<poll_id>/) with its own voting window and code universe, on one shared deployment. The vote transaction touches only the voter's own code document, so writes scale horizontally — capacity is thousands of votes per second; the election needs a small fraction of that.
PII & vote visibility (not a secret ballot)
Hybrid visibility, disclosed to voters on the ballot: poll questions and local issues (Sections 1 & 3) are recorded by name — election administrators see all ballots; each chapter sees its own members' votes. The delegate election (Section 2) is a secret ballot per Const. Art. V §5: rankings are stored in a separate admin-only collection with no name or chapter attached. It retains only a code hash so an election administrator can trace one specific ballot when troubleshooting — that access must be IAM-restricted and audit-logged, and chapters never get it. National does not publish any chapter's results. Each chapter receives its own results package and decides for itself whether to publish them — including named votes on Sections 1 & 3, but never delegate rankings. Voting codes are stored only as SHA-256 hashes.
Security & abuse resistance
The app sends nothing on demand, so a leaked link cannot run up messaging costs — assume links leak; only codes gate voting. Malformed codes are rejected before any database access. Code resends are enumeration-safe: a typed contact is a lookup key, never a destination, so sends only ever go to on-record contacts of real members, rate-limited per contact and IP. Production hardening: Cloud Armor rate limits, a max-instances cap (floods become slowness, not bills), and billing alerts. One code = one vote, enforced atomically.
Vote storage, tabulation & alternates
Each ballot records the answers, a receipt code, a nonce, and a content hash. After close, a batch job builds a tamper-evident hash chain over all ballots and publishes ballots.csv, used-code counts, and the chain head. Ranked questions are counted by Scottish STV (SSI 2007/42). Delegates use the expanded count for alternates (the default; an admin can switch a contest to the replacement re-run): an STV count for the delegate seats decides the delegation; the same ballots are recounted with delegate+alternate seats, and anyone elected there who is not already a delegate becomes an alternate, in order of election. Candidate order is randomly shuffled per voter.
Verification (internal — results not public)
After close, the export, hash chain, BLT files, and verifier run per chapter, and each chapter's admins receive their own results package. National does not publish chapter results; each chapter decides whether and how to publish its own — full results, named votes on Sections 1 & 3, or nothing — except delegate rankings, which stay secret regardless. Voters can confirm their receipt with the administrator; chapters can recount their own ballots in independent software (OpaVote, OpenSTV, the shipped tabulator) and check ballots against used codes.
Accessibility (WCAG 2.1 AA)
Automated audit (axe-core): 0 violations. All 13 color pairs pass AA contrast. Full keyboard operation including arrow-key radio groups; screen readers announce rank positions and status changes; no zoom lock. Status: passes everything automation can check; a recorded human screen-reader run (VoiceOver / TalkBack / NVDA) is required before claiming conformance publicly. Test report ships with the code.
How ballots reach voters & reminders
Channel ladder per member: email first (near-free); SMS if no email is on file; printed postcard with the code and a QR one-tap link if neither exists (mailed first, since it's slowest). Non-voters then get email and SMS reminders before close. All sends go through the distribution platform from a generated manifest — this app never sends messages itself.
Provisional ballots save support time. Voters who can't find a code self-serve from the ballot page: they cast a sealed provisional ballot with everything adjudicators need to match them — all emails and phones that might be on their membership, chapter, join date, and alternate names. Instead of staff live-troubleshooting each "I never got a code" ticket during the election, those cases queue for asynchronous batch adjudication, and the voter has already voted — no follow-up round-trip to get their ballot in.
Estimated all-in cost
Infrastructure is single-digit dollars (Firestore ~$1, Cloud Run a few dollars); email is roughly free. The real spend is SMS at $0.0125 per segment. Plan: ballots go by email first; members who haven't voted by email get an SMS, then one more, smaller SMS reminder to remaining non-voters. Worked example at 120k members: wave 1 ≈ 72k texts (non-voters after email), wave 2 ≈ 45k — ~117k segments ≈ $1,460 if the message fits one segment (keep it under 160 characters: code + short link), roughly double if it spills to two. Postcards for the no-email/no-phone tier (~2–3k members) add ~$2–3k printed and mailed. Realistic all-in: ~$2–5k, driven almost entirely by SMS volume, segment count, and postcards. Staff time for provisional adjudication is the real hidden cost.
Why RosaVote is the preferred alternative
OpaVote is a good partner to DSA — an affordable, dependable service that has run our elections for years, and a solid choice, especially for high-stakes or contested races where an independent third party adds trust. This app doesn't force a migration off it; it's the preferred alternative for most elections: stronger, more flexible, more customizable, and more affordable. (See the fuller side-by-side.)
More affordable at our scale. OpaVote prices per voter, per election (about $0.08/voter) — reasonable for one vote, but it multiplies across every chapter running its own election. This app runs on cloud DSA already operates, so the marginal cost of an election is a fraction of a vendor fee; message delivery (SMS/postcards) is the main variable, and that's an add-on either way.
Built for DSA's ballots. Because it's ours to shape, this app bakes in what OpaVote can only handle by hand: codes generated straight from the deduplicated membership roll (no list uploads), multi-section ballots with per-section visibility (named poll votes beside a secret delegate ballot on one page), diversity-quota reservations, Article V timing, the expanded-count alternates rule, chapter-unique questions, and randomized slates. There they're workarounds; here they're configuration.
Verifiable and owned. Same trusted Scottish STV math (OpaVote's descends from OpenSTV; ours implements the same statute, reading the same BLT files), but this app adds per-voter receipts and a public hash chain so members verify their own vote and anyone can recount — and the code, the data, and the roadmap belong to DSA, not a vendor. Ballots stay on DSA infrastructure instead of being deleted about 12 weeks after the election.
The trade. DSA runs the infrastructure itself — IAM, audit logs, hardening, an accountable administrator — where OpaVote hands you a fully hosted service. That's real work, and it's the price of owning our own democratic infrastructure. For most elections at our scale, it's worth it.
Not a forced migration. OpaVote is a good partner, and no chapter has to leave it. For an especially high-stakes or contested election, some will prefer the independence of a neutral third-party vendor with commercial support — and OpaVote stays an excellent choice there. RosaVote is the preferred default everywhere else, ready whenever a chapter is.
How this compares to OpenSlides
Different jobs. OpenSlides is a live assembly system — agenda, motions, amendments, speakers list, projector, and floor votes for delegates who are present (in the hall or logged in) during a meeting. This app is an asynchronous ballot: members vote on their own phones over days or weeks. They're complements, not substitutes — OpenSlides runs the convention floor; this runs the pre-convention delegate elections and chapter polls that Art. V requires chapters to conduct themselves.
Architecture & scale. Live assembly voting is one of the hardest real-time problems there is: every participant holds a persistent connection, and the whole room votes in the same ninety seconds on venue WiFi. This app sidesteps that entire class of challenge by being asynchronous and stateless: plain request/response, no persistent connections to manage, Cloud Run autoscaling, and voters spread over days instead of seconds — so 120,000 members is a lighter load than a single packed hall. Using each tool for the job it's built for keeps both at their best: OpenSlides where the room is live, this app where the electorate is distributed.
Voting features. OpenSlides handles in-meeting voting — Yes/No/Abstain and per-candidate (approval-style) polls, weighted and anonymous — alongside its core strengths in motions, amendments, quorum tracking, and the projector-ready floor process this app doesn't attempt. What it isn't built for today is ranked-choice STV elections (its poll methods are Yes/No/Abstain variants; ranked STV isn't among them yet) — and that's exactly this app's lane: Scottish & Meek STV, the expanded-count alternates recount, code-gated voting for 120k members with no accounts to provision, codes generated straight from the deduplicated membership roll, SMS and postcard delivery with reminder waves, self-serve provisional ballots, per-section visibility (named poll votes beside a secret delegate ballot), chapter-unique questions on one styled page, randomized candidate order, and per-chapter voting windows on a single deployment. Different tools for different jobs.
Cost & ops. Running a live assembly well takes real event engineering — hosting, load testing, venue network planning — because everything must work in the moment, in the room. Asynchronous voting is far more forgiving: this app is ~$10 of infrastructure plus message delivery, and if a voter's request hiccups they simply tap again — a resilience that comes free with the multi-day format.
Admin FAQ
Can test votes pollute results? No — the repeatable test codes above return a full voting experience but write nothing.
Can a user vote more than once? No. One code = one vote, enforced by an atomic transaction on the voter's code document — even two simultaneous submissions can't both succeed; the second gets "already voted." A member's email and phone lead to the same single code (codes are issued per deduplicated member, not per contact), and provisional ballots are checked against the member's code status at adjudication, so provisional + coded can't both count.
Can a user change their vote? No. Votes are final once cast — the ballot says so at review before casting. There is no edit or revote path in the software, administrators have no ballot-editing capability, and the post-close hash chain would make any altered or substituted ballot record evident.
What if a ballot is compromised (stolen code, technical failure)? Administrators can void and reissue — never edit. The cast ballot is flagged voided (kept forever in the record and the hash chain, excluded from tallies), an append-only audit entry records who/why/when, and the member gets a fresh code to vote again. Only while the poll is open, only for pre-committed reasons (stolen code, technical failure, provisional adjudication — never "changed my mind"), and aggregate void counts are disclosed at certification. Administrators control whether a ballot counts, with a paper trail — never what it says.
Who can see how someone voted? Poll questions & local issues: election administrators and the voter's chapter. Delegate rankings: no one by name — administrators can trace a specific delegate ballot only via the code mapping, for troubleshooting, under audit. Disclosed to voters on the ballot. National does not publish chapter results; each chapter decides whether to publish its own.
Can results be seen early? Live tallies are admin-only via the data warehouse (audited); nothing public until every chapter closes.
What if a voter never got a code? Resend to their on-record contact, or they cast a sealed provisional ballot with identifying details for staff verification.
What happens at close? Each chapter's poll closes on its own schedule; the close-out job exports, chains, hashes, and publishes that chapter's package; national results aggregate after all chapters close.