test_graph_quorum.py
python
sha256:9590cee1e0ccd6c76528f005b95d634d80f5019f0dcb7c371e149adc31d1fb65
refactor: enforce gRPC framing on all MWP wire traffic
Sonnet 4.6
minor
⚠ breaking
156 days ago
| 1 | """TDD — I3: Quorum soundness invariant. |
| 2 | |
| 3 | An org's vote in a parent org counts only if that org's own quorum |
| 4 | is independently satisfied. Recursive. Terminates because I1 guarantees |
| 5 | the graph is a DAG. |
| 6 | """ |
| 7 | from __future__ import annotations |
| 8 | |
| 9 | from decimal import Decimal |
| 10 | |
| 11 | import pytest |
| 12 | |
| 13 | from musehub.graph.dag import EdgeType, GraphEdge, IdentityDAG, NodeType |
| 14 | from musehub.graph.quorum import OrgSpec, QuorumEngine, VoteRecord |
| 15 | |
| 16 | |
| 17 | S = EdgeType.SPAWNS |
| 18 | M = EdgeType.MEMBER_OF |
| 19 | |
| 20 | |
| 21 | def spec(handle: str, quorum: int, members: dict[str, Decimal]) -> OrgSpec: |
| 22 | """Convenience constructor for OrgSpec.""" |
| 23 | return OrgSpec(handle=handle, quorum=quorum, member_weights=members) |
| 24 | |
| 25 | |
| 26 | def vote(voter: str, org: str) -> VoteRecord: |
| 27 | return VoteRecord(voter_handle=voter, org_handle=org) |
| 28 | |
| 29 | |
| 30 | # ── Simple flat orgs ────────────────────────────────────────────────────────── |
| 31 | |
| 32 | class TestFlatQuorum: |
| 33 | def test_quorum_met_exact_threshold(self): |
| 34 | # 3 members, quorum=2, exactly 2 vote |
| 35 | engine = QuorumEngine( |
| 36 | orgs={"acme": spec("acme", quorum=2, members={ |
| 37 | "alice": Decimal("1"), |
| 38 | "bob": Decimal("1"), |
| 39 | "carol": Decimal("1"), |
| 40 | })} |
| 41 | ) |
| 42 | votes = [vote("alice", "acme"), vote("bob", "acme")] |
| 43 | assert engine.is_quorum_met("acme", votes) is True |
| 44 | |
| 45 | def test_quorum_not_met_one_short(self): |
| 46 | engine = QuorumEngine( |
| 47 | orgs={"acme": spec("acme", quorum=2, members={ |
| 48 | "alice": Decimal("1"), |
| 49 | "bob": Decimal("1"), |
| 50 | "carol": Decimal("1"), |
| 51 | })} |
| 52 | ) |
| 53 | votes = [vote("alice", "acme")] |
| 54 | assert engine.is_quorum_met("acme", votes) is False |
| 55 | |
| 56 | def test_quorum_not_met_no_votes(self): |
| 57 | engine = QuorumEngine( |
| 58 | orgs={"acme": spec("acme", quorum=1, members={"alice": Decimal("1")})} |
| 59 | ) |
| 60 | assert engine.is_quorum_met("acme", []) is False |
| 61 | |
| 62 | def test_quorum_1_met_by_single_vote(self): |
| 63 | engine = QuorumEngine( |
| 64 | orgs={"acme": spec("acme", quorum=1, members={"alice": Decimal("1")})} |
| 65 | ) |
| 66 | assert engine.is_quorum_met("acme", [vote("alice", "acme")]) is True |
| 67 | |
| 68 | def test_unanimous_quorum(self): |
| 69 | engine = QuorumEngine( |
| 70 | orgs={"acme": spec("acme", quorum=3, members={ |
| 71 | "alice": Decimal("1"), |
| 72 | "bob": Decimal("1"), |
| 73 | "carol": Decimal("1"), |
| 74 | })} |
| 75 | ) |
| 76 | votes = [vote("alice", "acme"), vote("bob", "acme"), vote("carol", "acme")] |
| 77 | assert engine.is_quorum_met("acme", votes) is True |
| 78 | |
| 79 | def test_non_member_vote_does_not_count(self): |
| 80 | engine = QuorumEngine( |
| 81 | orgs={"acme": spec("acme", quorum=1, members={"alice": Decimal("1")})} |
| 82 | ) |
| 83 | # dave is not a member of acme |
| 84 | assert engine.is_quorum_met("acme", [vote("dave", "acme")]) is False |
| 85 | |
| 86 | |
| 87 | # ── Weighted members ────────────────────────────────────────────────────────── |
| 88 | |
| 89 | class TestWeightedQuorum: |
| 90 | def test_weighted_vote_reaches_quorum(self): |
| 91 | # alice has weight 2, quorum=2 → alice alone satisfies it |
| 92 | engine = QuorumEngine( |
| 93 | orgs={"acme": spec("acme", quorum=2, members={ |
| 94 | "alice": Decimal("2"), |
| 95 | "bob": Decimal("1"), |
| 96 | })} |
| 97 | ) |
| 98 | assert engine.is_quorum_met("acme", [vote("alice", "acme")]) is True |
| 99 | |
| 100 | def test_low_weight_vote_does_not_reach_quorum(self): |
| 101 | engine = QuorumEngine( |
| 102 | orgs={"acme": spec("acme", quorum=3, members={ |
| 103 | "alice": Decimal("1"), |
| 104 | "bob": Decimal("1"), |
| 105 | "carol": Decimal("1"), |
| 106 | })} |
| 107 | ) |
| 108 | # only alice (weight 1) voted — needs 3 |
| 109 | assert engine.is_quorum_met("acme", [vote("alice", "acme")]) is False |
| 110 | |
| 111 | def test_fractional_weights_sum_to_quorum(self): |
| 112 | engine = QuorumEngine( |
| 113 | orgs={"acme": spec("acme", quorum=1, members={ |
| 114 | "alice": Decimal("0.5"), |
| 115 | "bob": Decimal("0.5"), |
| 116 | })} |
| 117 | ) |
| 118 | votes = [vote("alice", "acme"), vote("bob", "acme")] |
| 119 | assert engine.is_quorum_met("acme", votes) is True |
| 120 | |
| 121 | |
| 122 | # ── Nested orgs — quorum propagation ───────────────────────────────────────── |
| 123 | |
| 124 | class TestNestedQuorum: |
| 125 | def _two_level_engine(self) -> QuorumEngine: |
| 126 | # parent-org has [alice, sub-org] as members, quorum=2 |
| 127 | # sub-org has [bob, carol] as members, quorum=1 |
| 128 | return QuorumEngine(orgs={ |
| 129 | "parent-org": spec("parent-org", quorum=2, members={ |
| 130 | "alice": Decimal("1"), |
| 131 | "sub-org": Decimal("1"), |
| 132 | }), |
| 133 | "sub-org": spec("sub-org", quorum=1, members={ |
| 134 | "bob": Decimal("1"), |
| 135 | "carol": Decimal("1"), |
| 136 | }), |
| 137 | }) |
| 138 | |
| 139 | def test_org_vote_counts_when_its_quorum_met(self): |
| 140 | engine = self._two_level_engine() |
| 141 | # alice votes in parent; bob votes in sub-org (satisfying sub-org quorum=1) |
| 142 | # sub-org's vote in parent then counts → parent total = 2 → met |
| 143 | votes = [ |
| 144 | vote("alice", "parent-org"), |
| 145 | vote("bob", "sub-org"), |
| 146 | vote("sub-org", "parent-org"), |
| 147 | ] |
| 148 | assert engine.is_quorum_met("parent-org", votes) is True |
| 149 | |
| 150 | def test_org_vote_does_not_count_when_its_quorum_not_met(self): |
| 151 | engine = self._two_level_engine() |
| 152 | # sub-org votes in parent, but nobody voted inside sub-org → sub-org quorum not met |
| 153 | votes = [ |
| 154 | vote("alice", "parent-org"), |
| 155 | vote("sub-org", "parent-org"), |
| 156 | ] |
| 157 | assert engine.is_quorum_met("parent-org", votes) is False |
| 158 | |
| 159 | def test_parent_quorum_not_met_even_if_sub_quorum_met(self): |
| 160 | engine = self._two_level_engine() |
| 161 | # sub-org quorum met (bob voted) but only sub-org votes in parent → total=1 < quorum=2 |
| 162 | votes = [ |
| 163 | vote("bob", "sub-org"), |
| 164 | vote("sub-org", "parent-org"), |
| 165 | ] |
| 166 | assert engine.is_quorum_met("parent-org", votes) is False |
| 167 | |
| 168 | def test_three_level_nesting_all_quorums_met(self): |
| 169 | engine = QuorumEngine(orgs={ |
| 170 | "top": spec("top", quorum=2, members={"alice": Decimal("1"), "mid": Decimal("1")}), |
| 171 | "mid": spec("mid", quorum=1, members={"bob": Decimal("1"), "bot": Decimal("1")}), |
| 172 | "bot": spec("bot", quorum=1, members={"carol": Decimal("1")}), |
| 173 | }) |
| 174 | # carol votes in bot → bot quorum met |
| 175 | # bot votes in mid → mid quorum met |
| 176 | # alice + mid vote in top → top quorum met |
| 177 | votes = [ |
| 178 | vote("carol", "bot"), |
| 179 | vote("bot", "mid"), |
| 180 | vote("bob", "mid"), # extra, doesn't hurt |
| 181 | vote("mid", "top"), |
| 182 | vote("alice", "top"), |
| 183 | ] |
| 184 | assert engine.is_quorum_met("top", votes) is True |
| 185 | |
| 186 | def test_three_level_nesting_middle_quorum_not_met(self): |
| 187 | engine = QuorumEngine(orgs={ |
| 188 | "top": spec("top", quorum=2, members={"alice": Decimal("1"), "mid": Decimal("1")}), |
| 189 | "mid": spec("mid", quorum=2, members={"bob": Decimal("1"), "carol": Decimal("1")}), |
| 190 | }) |
| 191 | # only bob voted in mid → mid quorum (needs 2) not met → mid vote in top doesn't count |
| 192 | votes = [ |
| 193 | vote("bob", "mid"), |
| 194 | vote("mid", "top"), |
| 195 | vote("alice", "top"), |
| 196 | ] |
| 197 | assert engine.is_quorum_met("top", votes) is False |
| 198 | |
| 199 | |
| 200 | # ── Effective weight ────────────────────────────────────────────────────────── |
| 201 | |
| 202 | class TestEffectiveWeight: |
| 203 | def test_human_effective_weight_is_direct(self): |
| 204 | engine = QuorumEngine(orgs={ |
| 205 | "acme": spec("acme", quorum=1, members={"alice": Decimal("2")}) |
| 206 | }) |
| 207 | assert engine.effective_weight("acme", "alice", []) == Decimal("2") |
| 208 | |
| 209 | def test_org_effective_weight_zero_when_quorum_not_met(self): |
| 210 | engine = QuorumEngine(orgs={ |
| 211 | "parent": spec("parent", quorum=1, members={"sub": Decimal("3")}), |
| 212 | "sub": spec("sub", quorum=1, members={"alice": Decimal("1")}), |
| 213 | }) |
| 214 | # sub's quorum not met (alice hasn't voted in sub) → sub weight in parent = 0 |
| 215 | assert engine.effective_weight("parent", "sub", []) == Decimal("0") |
| 216 | |
| 217 | def test_org_effective_weight_is_direct_when_quorum_met(self): |
| 218 | engine = QuorumEngine(orgs={ |
| 219 | "parent": spec("parent", quorum=1, members={"sub": Decimal("3")}), |
| 220 | "sub": spec("sub", quorum=1, members={"alice": Decimal("1")}), |
| 221 | }) |
| 222 | votes = [vote("alice", "sub"), vote("sub", "parent")] |
| 223 | assert engine.effective_weight("parent", "sub", votes) == Decimal("3") |
| 224 | |
| 225 | |
| 226 | # ── Unknown org ─────────────────────────────────────────────────────────────── |
| 227 | |
| 228 | class TestUnknownOrg: |
| 229 | def test_unknown_org_raises(self): |
| 230 | engine = QuorumEngine(orgs={}) |
| 231 | with pytest.raises(KeyError): |
| 232 | engine.is_quorum_met("ghost-org", []) |
File History
1 commit
sha256:9590cee1e0ccd6c76528f005b95d634d80f5019f0dcb7c371e149adc31d1fb65
refactor: enforce gRPC framing on all MWP wire traffic
Sonnet 4.6
minor
⚠
156 days ago