Sealed-ballot motions: commit-reveal over gamecult.sealed_ballot.v0 #21
Loading…
Reference in a new issue
No description provided.
Delete branch "heimdall/sealed-ballot-motion"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Sealed-ballot motion for a second read. No deploy, no merge. Full design and decisions:
docs/sealed-ballot-motion.md.What it is. Commit-reveal over a typed ballot document
gamecult.sealed_ballot.v0(Libby's addendum, Heimdall leaning the same way). ExtendsMotion(newKind, deadlines, candidates,Round/PriorMotionIdfor a Delphi round) and addsSealedBallot. Bifrost stores only the hash during the commit window; reveals are stored but unreadable by any application path until close; the first/resultread after the deadline closes the motion and publishes every ballot at once.Decisions stated: cast =
POST /api/sealed-motions/{id}/committhen/reveal, typed JSON direct to Bifrost, never room text. Moderator opens; nobody closes (the deadline does); deadlines immutable. All active members eligible, self-votes allowed, one-line reason ≤280 chars. Non-voters and unrevealed commitments are shown at close as abstentions, never as votes. Re-commit during the commit window replaces the ballot. Ties are reported as ties; a runoff is a new motion. Commit-reveal was picked over a close-time key because the key holder is a mediator who sees every plaintext from the first cast. No custody opt-in offered (it would give back exactly that).Who can read the sealed store before close. Commit window: nobody (hashes only). Reveal window: the host, anyone with DB access, and any database backup taken in that window (it holds reveals already submitted). Named gap, not closed here. This repo documents only deploy-window
pg_dump -Fc; a nightly schedule is not stated here (it would live in gamecult-ops, unread), so I do not claim one. Detail in the doc.Tests (
SealedBallotTests9,SealedBallotEndpointTests3, plus migration list; 15 pass): full commit/reveal/close with each published ballot re-verified against its commitment; no leak of reason or salt through sealed state, audit events, the standard motion state, or the dashboard snapshot during the reveal window; a source scan that fails if any file other than the service and model touches the revealed columns; one ballot per voter and replace-on-recommit; abstention/non-voter display; ties; phase, role and old-vote-path gates. Full suite: 38 failures, the same 38 onmainwithout this change (BridgeCli, ConceptTallyExport, ConceptVoteRateLimit, OidcIssuer, ProviderAdvertisement; the CultCache TS runtime is absent here, rest not diagnosed). Tests ran on .NET 10 withDOTNET_ROLL_FORWARD=Major; the app targets 8.Update
96da1b7(second pass):Motion.Statusis an EF concurrency token; losers reload and write no second audit event. Tests: stale-copy conflict and 4 concurrent first reads close once; with the token removed the conflict test fails. Snapshot annotation only, no schema change.SealedBallotClosersweep, so an unread motion still closes (test onFinalizeDueAsync).Results.Textlike the Eve surface, becauseResults.Ok/Jsonhit a PipeWriter error on the .NET 10 runtime used here.Program.cs:176); anonymousPOST /auth/bifrost/register(Program.cs:358) gives a cookie but onlyAuthenticatedstatus (BifrostIdentityService.cs:49), approval needed; an existing identity cannot sign back in (BifrostIdentityService.cs:26), so a lost cookie jar is unrecoverable natively; no bearer scheme; the bridge token is read by no server code. No new auth path invented. Detail with lines in the doc.Update
55b7f79(Libby, round 3): a reveal that loses aRevealSeqrace while the window is still open is re-read and retried (up to 5, then 409 "contended; send it again") instead of being reported as "the motion closed"; a close that is truly over still answers that. Test: two simultaneous reveals both land (fails on the old code)./resultcan answer 409 "contended" if its close loses 5 races; documented. Hex patterns use\z(a salt with a trailing newline no longer passes; test fails on the old pattern).Unchecked / not built:
POST /api/sealed-motions) and itsOpenSealedMotionCommandbinding; commit, reveal, state, result are covered.dotnet-efhere), snapshot edited by hand, no Designer file. Never run against Postgres; only the in-memory provider and migration discovery ran.🤖 Generated with Claude Code