Public: anyone can read this; the crew can change it.
Three quorum rules as one pure module: majority, all, a named few #11
Check · Review · Land · Live done — Landed and live in production.
`src/quorum.ts` is the whole change, plus its test: two new files, nothing else in the repository touched, `bun test` green (32 tests, 14 of them new) and no new dependency.
shipped production is serving it deploy 2adbe7d
Sign in to join this conversation.
src/quorum.tsis the whole change, plus its test: two new files, nothing else in the repository touched,bun testgreen (32 tests, 14 of them new) and no new dependency.The module is pure data in, pure data out — a roster of person ids, a list of option ids and a list of ballots go in;
{decided, winner?, counts, needed, of, reason}comes out.parseRuleis the only reader of the stored rule text (majority,all,named:alice,bob,carol:2) and returnsundefinedrather than throwing for an unknown kind, an empty or repeated name, a name over 64 characters or outside[A-Za-z0-9_.-], more than 32 names, aneededoutside1..names.length, text longer than any rule this file can write, and anything that is not a string. A majority isMath.floor(of / 2) + 1— integers throughout, no float comparison anywhere.reasonis built only from integers and sentences the module owns, so a page can print it without escaping surprises.Worth looking at:
countsis an array in the order the options were given, one row per option id withvotes(every ballot, whoever cast it) andcounting(only ballots that move the decision) — that split is how an outsider's vote stays visible without touchingdecided. Anamedrule is measured against its names, not the roster, per the issue's wording; keeping those names inside the roster is the caller's job and I said so in the module's doc comment, since enforcing it here would need a roster the rule text does not have. I added two small things beyond the checklist and would rather flag them than hide them:formatRule, so the caller has one canonical spelling to store and the tests can round-trip, and a last-ballot-wins fold so a person appearing twice counts once (theballottable'sUNIQUE (proposal_id, voter_id)in issue #5 makes that unreachable in practice, but the function is total either way). I chose not to touchactions.ts,db.tsor any page: wiring this into a vote is issue #5, which waits on this one.Nothing to click in the preview — this lands no route.
bun test src/quorum.test.tsis the thing to read: the table names each refusal, and every rule is exercised just below, exactly at and above its threshold, along with a tie, an outsider's ballot, an empty roster and a roster of one.🤖 Generated with Claude Code
Closes #3