Public: anyone can read this; the crew can change it.

Three quorum rules as one pure module: majority, all, a named few #3

shipped production is serving it opened this has it
opened this

src/quorum.ts answers, from a roster and a set of ballots, whether a proposal is decided and by what — with no database, no HTTP and no Who anywhere in it.

What done looks like

  • [ ] src/quorum.ts exports RULES (the three names, each with one line of human description), parseRule(text) and tally(input), all pure functions over plain data.
  • [ ] A rule is {kind:"majority"} | {kind:"all"} | {kind:"named", who: string[], needed: number}, stored as text: majority, all, named:alice,bob,carol:2. parseRule is the only thing that reads that text, and it returns undefined for anything else — an unknown kind, an empty name list, needed below 1 or above the list's length, a name over 64 characters, more than 32 names, a name outside [A-Za-z0-9_.-]. It never throws.
  • [ ] tally({rule, roster, ballots, optionIds}) returns {decided, winner?, counts, needed, of, reason}, where counts covers every option id (zero included) and reason is one sentence a page can print verbatim: "4 of 7 — a majority needs 4", "2 of 3 named — this needs 2", "6 of 7 voted — all must agree".
  • [ ] majority: more than half the roster on one option decides it. all: every member of the roster has voted and all on the same option. named: needed of the named people on one option, and only their ballots count toward deciding, though every ballot shows in counts.
  • [ ] A ballot from someone outside the roster (or outside the names, for named) never moves decided. A tie decides nothing. An empty roster decides nothing. A roster of one under majority decides on one ballot.
  • [ ] Integer arithmetic only: a majority needs Math.floor(of / 2) + 1, never a float comparison.
  • [ ] src/quorum.test.ts is a table of cases: each rule just below, exactly at and above its threshold; a tie; an outsider's ballot; an empty roster; and every refusal parseRule makes.
  • [ ] Nothing else in the repository changes, and bun test is green.

Files I expect to change: src/quorum.ts (new), src/quorum.test.ts (new).

Risks: if this module needs a Database or a Who, the shape is wrong — the caller does the reading and hands it plain arrays. The stored rule text is user input arriving from another issue's door, so parseRule refusing cleanly is what keeps that safe. Keep the returned reason free of anything a person typed, so a page can print it without a surprise.

Fit: Ledger — totals recomputed from lines, thresholds exact, refusals named. A person assigns; I do not.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs