gabriel / musehub public
quorum.py python
76 lines 2.5 KB
Raw
sha256:da49a05fd62cda46a7d73ec53a8d0adc5835d2070f6f0c51b12233a673c2e109 docs(mwp-1): mark Phase 5 complete, all acceptance criteria… Sonnet 4.6 25 days ago
1 """I3 — Quorum soundness invariant.
2
3 An org's vote in a parent org counts only if that org's own quorum is
4 independently satisfied. Recursive. Terminates because I1 guarantees the
5 graph is a DAG (no circular org membership).
6 """
7
8 from collections.abc import Mapping
9 from dataclasses import dataclass
10 from decimal import Decimal
11
12 @dataclass(frozen=True)
13 class OrgSpec:
14 handle: str
15 quorum: int
16 member_weights: dict[str, Decimal]
17
18 @dataclass(frozen=True)
19 class VoteRecord:
20 voter_handle: str
21 org_handle: str
22
23 class QuorumEngine:
24 def __init__(self, orgs: Mapping[str, OrgSpec]) -> None:
25 self._orgs = orgs
26
27 def is_quorum_met(self, org_handle: str, votes: list[VoteRecord]) -> bool:
28 if org_handle not in self._orgs:
29 raise KeyError(org_handle)
30 total = self._tally(org_handle, votes, frozenset())
31 return total >= self._orgs[org_handle].quorum
32
33 def effective_weight(
34 self,
35 org_handle: str,
36 member_handle: str,
37 votes: list[VoteRecord],
38 ) -> Decimal:
39 spec = self._orgs[org_handle]
40 direct_weight = spec.member_weights.get(member_handle, Decimal("0"))
41 if direct_weight == Decimal("0"):
42 return Decimal("0")
43
44 # If member is itself an org, its weight is only active when its quorum is met
45 if member_handle in self._orgs:
46 if not self.is_quorum_met(member_handle, votes):
47 return Decimal("0")
48
49 return direct_weight
50
51 # ── internals ─────────────────────────────────────────────────────────────
52
53 def _tally(
54 self,
55 org_handle: str,
56 votes: list[VoteRecord],
57 visited: frozenset[str],
58 ) -> Decimal:
59 spec = self._orgs[org_handle]
60 # Collect votes cast directly for this org
61 voters_here = {v.voter_handle for v in votes if v.org_handle == org_handle}
62
63 total = Decimal("0")
64 for voter in voters_here:
65 w = spec.member_weights.get(voter, Decimal("0"))
66 if w == Decimal("0"):
67 continue
68 # If this voter is itself an org, validate its quorum recursively
69 if voter in self._orgs and voter not in visited:
70 sub_tally = self._tally(voter, votes, visited | {org_handle})
71 sub_quorum = self._orgs[voter].quorum
72 if sub_tally < sub_quorum:
73 continue
74 total += w
75
76 return total
File History 1 commit
sha256:da49a05fd62cda46a7d73ec53a8d0adc5835d2070f6f0c51b12233a673c2e109 docs(mwp-1): mark Phase 5 complete, all acceptance criteria… Sonnet 4.6 25 days ago