{"messages":[{"content":"You are a constitutional council ranking individual git commits for ownership allocation.\n\nCompare these two commits. Decide which contributed more lasting value to the project.\n\nJudge substance, not spectacle:\n- Prefer correct, lasting design and real bugfixes over churn, formatting, renames, or generated noise.\n- Prefer clarity and necessity over sheer line count. A small precise change can beat a large diffuse one.\n- Do not favor a side merely because its patch is longer or noisier.\n- Weight what the change does for the project, not the contributor's name.\n\nReturn ONLY a JSON object: {\"winner\": \"A\" or \"B\", \"ratio\": \"N:M\", \"explanation\": \"...\"}\nThe explanation must cite concrete differences in the patches (1-3 sentences).\n\nSide A — contributor: tommy-mor\nSide A — commit message:\n[d982f49a] Simplify pair suggestion to avoid O(n²) rank-centrality work.\n\nGrow the voted graph with linear attach/bridge scans, then zip adjacent\nranks with a single RC pass once the pool is fully connected.\n\nCo-authored-by: Cursor \n\nSide A — unified diff (full patch):\ndiff --git a/server/src/pair.rs b/server/src/pair.rs\nindex c14de4b0502c8b5a17cddf3746077739d56e03e0..0687073821b93f7fef69c453f8f921ed5ddfca64 100644\n--- a/server/src/pair.rs\n+++ b/server/src/pair.rs\n@@ -1,16 +1,10 @@\n //! Pick two children of a parent scope for pairwise voting.\n //!\n-//! Pair selection prefers **bridge** votes — comparisons between items in\n-//! different connected components of the voted-pairs graph — so the pool\n-//! merges into one ranking group before refining within it.\n+//! Before the pool is one connected voted component, prefer an unvoted edge from\n+//! a never-voted child into an established group (spanning tree growth).\n //!\n-//! Among unvoted bridges, prefer merging established voted components, then\n-//! attaching a never-voted child to an established component, and only then\n-//! comparing two never-voted children (so the voted graph grows as one tree).\n-//!\n-//! Once every pool child sits in one voted component, refinement **zips** down\n-//! the rank-centrality order: prefer 1 vs 2, then 2 vs 3, and so on, skipping\n-//! pairs that already have a vote.\n+//! Once connected, run rank centrality once and **zip** adjacent ranks (1 vs 2,\n+//! 2 vs 3, …), skipping pairs that already have a vote.\n \n use rand::seq::SliceRandom;\n use std::collections::{HashMap, HashSet};\n@@ -25,6 +19,10 @@ fn pairs_match(a: &ItemId, b: &ItemId, x: &ItemId, y: &ItemId) -> bool {\n (a == x && b == y) || (a == y && b == x)\n }\n \n+fn pair_excluded(a: &ItemId, b: &ItemId, exclude: Option<(&ItemId, &ItemId)>) -> bool {\n+ exclude.is_some_and(|(x, y)| pairs_match(a, b, x, y))\n+}\n+\n fn pair_is_voted(group: &GroupState, a: &ItemId, b: &ItemId) -> bool {\n let Some(&ai) = group.item_to_idx.get(a) else {\n return false;\n@@ -36,8 +34,6 @@ fn pair_is_voted(group: &GroupState, a: &ItemId, b: &ItemId) -> bool {\n group.voted_pairs.contains(&(i, j))\n }\n \n-/// Voted-pairs layout for pool items: component id per item plus which ids are\n-/// multi-node voted components (ranked groups in the UI).\n struct ComponentLayout {\n ids: HashMap,\n established: HashSet,\n@@ -77,7 +73,6 @@ fn component_layout(group: &GroupState, pool: &[ItemId]) -> ComponentLayout {\n ComponentLayout { ids, established }\n }\n \n-/// Every pool child shares one multi-node voted component (spanning tree phase done).\n fn pool_fully_connected(layout: &ComponentLayout, pool: &[ItemId]) -> bool {\n if pool.len() < 2 {\n return false;\n@@ -99,7 +94,13 @@ fn pool_fully_connected(layout: &ComponentLayout, pool: &[ItemId]) -> bool {\n comp_id.is_some()\n }\n \n-/// Pool children that appear in `group`, sorted best rank first.\n+fn item_in_established(layout: &ComponentLayout, item: &ItemId) -> bool {\n+ layout\n+ .ids\n+ .get(item)\n+ .is_some_and(|id| layout.established.contains(id))\n+}\n+\n fn ranked_pool_order(group: &GroupState, pool: &[ItemId]) -> Vec {\n let pool_set: HashSet<_> = pool.iter().collect();\n ranked_items(group)\n@@ -109,170 +110,126 @@ fn ranked_pool_order(group: &GroupState, pool: &[ItemId]) -> Vec {\n .collect()\n }\n \n-#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]\n-enum PairPriority {\n- /// Unvoted edge between two components — grows the ranking group.\n- BridgeUnvoted = 0,\n- /// Unvoted edge inside one component — refines order.\n- WithinUnvoted = 1,\n- /// Re-vote across components (rare once merged).\n- BridgeVoted = 2,\n- /// Re-vote within a component.\n- WithinVoted = 3,\n-}\n-\n-/// Tie-break among unvoted bridge pairs.\n-#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]\n-enum BridgeSubPriority {\n- /// Both endpoints lie in established (multi-node) voted components.\n- MergeEstablished = 0,\n- /// One established component member and one never-voted child.\n- AttachIsolate = 1,\n- /// Two never-voted children (separate singleton components).\n- IsolatePair = 2,\n-}\n-\n-/// Tie-break among within-component pairs once the pool is one connected group.\n-#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]\n-struct WithinSubPriority {\n- /// 1 = adjacent ranks (i vs i+1); larger = farther apart in the order.\n- rank_gap: usize,\n- /// min rank index of the two — zip from the top (1 vs 2 before 2 vs 3).\n- zip_index: usize,\n-}\n-\n-const WITHIN_SUB_WORST: WithinSubPriority = WithinSubPriority {\n- rank_gap: usize::MAX,\n- zip_index: usize::MAX,\n-};\n-\n-#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]\n-struct PairSortKey {\n- priority: PairPriority,\n- bridge_sub: BridgeSubPriority,\n- within_sub: WithinSubPriority,\n-}\n-\n-fn item_in_established(layout: &ComponentLayout, item: &ItemId) -> bool {\n- layout\n- .ids\n- .get(item)\n- .is_some_and(|id| layout.established.contains(id))\n-}\n-\n-fn bridge_sub_priority(layout: &ComponentLayout, a: &ItemId, b: &ItemId) -> BridgeSubPriority {\n- let a_est = item_in_established(layout, a);\n- let b_est = item_in_established(layout, b);\n- match (a_est, b_est) {\n- (true, true) => BridgeSubPriority::MergeEstablished,\n- (true, false) | (false, true) => BridgeSubPriority::AttachIsolate,\n- (false, false) => BridgeSubPriority::IsolatePair,\n+/// Walk 1↔2, 2↔3, …; optional `require_unvoted` skips voted edges.\n+fn zip_adjacent_pair(\n+ group: &GroupState,\n+ order: &[ItemId],\n+ exclude: Option<(&ItemId, &ItemId)>,\n+ require_unvoted: bool,\n+) -> Option<(ItemId, ItemId)> {\n+ for w in order.windows(2) {\n+ let a = &w[0];\n+ let b = &w[1];\n+ if pair_excluded(a, b, exclude) {\n+ continue;\n+ }\n+ if require_unvoted && pair_is_voted(group, a, b) {\n+ continue;\n+ }\n+ return Some((a.clone(), b.clone()));\n }\n+ None\n }\n \n-fn within_sub_priority(\n+/// Grow the voted graph toward one component (no rank centrality).\n+fn suggest_grow_pair(\n group: &GroupState,\n pool: &[ItemId],\n layout: &ComponentLayout,\n- a: &ItemId,\n- b: &ItemId,\n-) -> WithinSubPriority {\n- if !pool_fully_connected(layout, pool) {\n- return WITHIN_SUB_WORST;\n- }\n- let order = ranked_pool_order(group, pool);\n- let (Some(i), Some(j)) = (order.iter().position(|x| x == a), order.iter().position(|x| x == b))\n- else {\n- return WITHIN_SUB_WORST;\n- };\n- WithinSubPriority {\n- rank_gap: i.abs_diff(j),\n- zip_index: i.min(j),\n+ exclude: Option<(&ItemId, &ItemId)>,\n+) -> Option<(ItemId, ItemId)> {\n+ let established: Vec<&ItemId> = pool\n+ .iter()\n+ .filter(|item| item_in_established(layout, item))\n+ .collect();\n+ let isolates: Vec<&ItemId> = pool\n+ .iter()\n+ .filter(|item| !item_in_established(layout, item))\n+ .collect();\n+\n+ // Attach a never-voted child to the established mass.\n+ for iso in &isolates {\n+ for est in &established {\n+ if !pair_is_voted(group, iso, est) && !pair_excluded(iso, est, exclude) {\n+ return Some(((*iso).clone(), (*est).clone()));\n+ }\n+ }\n }\n-}\n \n-fn pair_sort_key(\n- group: &GroupState,\n- pool: &[ItemId],\n- layout: &ComponentLayout,\n- a: &ItemId,\n- b: &ItemId,\n-) -> PairSortKey {\n- let voted = pair_is_voted(group, a, b);\n- let bridge = layout.ids.get(a) != layout.ids.get(b);\n- let priority = match (bridge, voted) {\n- (true, false) => PairPriority::BridgeUnvoted,\n- (false, false) => PairPriority::WithinUnvoted,\n- (true, true) => PairPriority::BridgeVoted,\n- (false, true) => PairPriority::WithinVoted,\n- };\n- let bridge_sub = if priority == PairPriority::BridgeUnvoted {\n- bridge_sub_priority(layout, a, b)\n- } else {\n- BridgeSubPriority::MergeEstablished\n- };\n- let within_sub = if matches!(\n- priority,\n- PairPriority::WithinUnvoted | PairPriority::WithinVoted\n- ) {\n- within_sub_priority(group, pool, layout, a, b)\n- } else {\n- WITHIN_SUB_WORST\n- };\n- PairSortKey {\n- priority,\n- bridge_sub,\n- within_sub,\n+ // Bridge two established components.\n+ for i in 0..established.len() {\n+ for j in (i + 1)..established.len() {\n+ let a = established[i];\n+ let b = established[j];\n+ if layout.ids.get(a) == layout.ids.get(b) {\n+ continue;\n+ }\n+ if !pair_is_voted(group, a, b) && !pair_excluded(a, b, exclude) {\n+ return Some((a.clone(), b.clone()));\n+ }\n+ }\n }\n-}\n \n-/// All unordered pairs from `pool`, optionally skipping `exclude`.\n-fn candidate_pairs(pool: &[ItemId], exclude: Option<(&ItemId, &ItemId)>) -> Vec<(ItemId, ItemId)> {\n- let mut out = Vec::new();\n+ // Any other unvoted pair (e.g. two isolates).\n for i in 0..pool.len() {\n for j in (i + 1)..pool.len() {\n let a = &pool[i];\n let b = &pool[j];\n- if a == b {\n- continue;\n- }\n- if exclude.is_some_and(|(x, y)| pairs_match(a, b, x, y)) {\n+ if pair_is_voted(group, a, b) || pair_excluded(a, b, exclude) {\n continue;\n }\n- out.push((a.clone(), b.clone()));\n+ return Some((a.clone(), b.clone()));\n }\n }\n- out\n+\n+ None\n }\n \n /// Pick the next pair to vote on within `pool`.\n-///\n-/// 1. Prefer unvoted **bridge** pairs (connect separate ranking components),\n-/// with sub-priority: merge established components, attach an isolate to\n-/// established, then compare two isolates.\n-/// 2. Then unvoted within-component pairs; when the pool is one connected group,\n-/// prefer adjacent ranks (1 vs 2, 2 vs 3, …) in order, skipping voted pairs.\n-/// 3. Then already-voted pairs (re-compare), with the same zip ordering.\n pub fn suggest_next_pair_in_pool(\n group: &GroupState,\n pool: &[ItemId],\n exclude: Option<(&ItemId, &ItemId)>,\n ) -> Option<(ItemId, ItemId)> {\n- let candidates = candidate_pairs(pool, exclude);\n- if candidates.is_empty() {\n+ if pool.len() < 2 {\n return None;\n }\n+\n let layout = component_layout(group, pool);\n- let best = candidates\n- .iter()\n- .map(|(a, b)| (pair_sort_key(group, pool, &layout, a, b), (a, b)))\n- .min_by_key(|(k, _)| *k)?\n- .0;\n- let best_pairs: Vec<(ItemId, ItemId)> = candidates\n- .into_iter()\n- .filter(|(a, b)| pair_sort_key(group, pool, &layout, a, b) == best)\n- .collect();\n- best_pairs.choose(&mut rand::thread_rng()).cloned()\n+\n+ if pool_fully_connected(&layout, pool) {\n+ let order = ranked_pool_order(group, pool);\n+ if let Some(pair) = zip_adjacent_pair(group, &order, exclude, true) {\n+ return Some(pair);\n+ }\n+ if let Some(pair) = zip_adjacent_pair(group, &order, exclude, false) {\n+ return Some(pair);\n+ }\n+ }\n+\n+ if let Some(pair) = suggest_grow_pair(group, pool, &layout, exclude) {\n+ return Some(pair);\n+ }\n+\n+ // Re-vote: zip order when connected, else any non-excluded pair.\n+ if pool_fully_connected(&layout, pool) {\n+ let order = ranked_pool_order(group, pool);\n+ if let Some(pair) = zip_adjacent_pair(group, &order, exclude, false) {\n+ return Some(pair);\n+ }\n+ }\n+\n+ for i in 0..pool.len() {\n+ for j in (i + 1)..pool.len() {\n+ let a = &pool[i];\n+ let b = &pool[j];\n+ if !pair_excluded(a, b, exclude) {\n+ return Some((a.clone(), b.clone()));\n+ }\n+ }\n+ }\n+\n+ None\n }\n \n /// Random distinct pair from `children` (legacy pair.rs behavior).\n@@ -509,7 +466,6 @@ mod tests {\n \"reddit.com/r/rust/c\",\n ],\n );\n- // Star at a connects all three; b-c is the only unvoted adjacent pair left.\n for (a, b, l, r) in [\n (\"reddit.com/r/rust/a\", \"reddit.com/r/rust/b\", 3, 1),\n (\"reddit.com/r/rust/a\", \"reddit.com/r/rust/c\", 2, 1),\n@@ -521,7 +477,6 @@ mod tests {\n let pool = children_of(&tree, &parent);\n let pair = suggest_next_pair_in_pool(&group, &pool, None).unwrap();\n let chosen = pair_set(&pair);\n- // a-b and a-c voted; b-c is the only unvoted adjacent pair in rank order.\n assert!(chosen.contains(\"reddit.com/r/rust/b\"));\n assert!(chosen.contains(\"reddit.com/r/rust/c\"));\n }\n@@ -538,7 +493,6 @@ mod tests {\n \"reddit.com/r/rust/d\",\n ],\n );\n- // Hub at c connects all four; leave rank-adjacent a-b and b-c unvoted.\n for (a, b, l, r) in [\n (\"reddit.com/r/rust/c\", \"reddit.com/r/rust/d\", 3, 1),\n (\"reddit.com/r/rust/b\", \"reddit.com/r/rust/c\", 2, 1),\n@@ -549,10 +503,8 @@ mod tests {\n }\n let group = tree.get(&parent).unwrap().local_ranking.clone();\n let pool = children_of(&tree, &parent);\n- assert!(pool_fully_connected(&component_layout(&group, &pool), &pool));\n let pair = suggest_next_pair_in_pool(&group, &pool, None).unwrap();\n let chosen = pair_set(&pair);\n- // Top adjacent unvoted edge should be a-b (zip index 0), not b-c (index 1).\n assert!(chosen.contains(\"reddit.com/r/rust/a\"));\n assert!(chosen.contains(\"reddit.com/r/rust/b\"));\n }\n\n\nSide B — contributor: tommy-mor\nSide B — commit message:\n[d99feb62] Randomly sample attach targets from the largest voted group.\n\nAvoid always pairing new items with the same established endpoint by\nshuffling isolates and picking a random eligible member of the biggest\nconnected voted component.\n\nCo-authored-by: Cursor \n\nSide B — unified diff (full patch):\ndiff --git a/server/src/pair.rs b/server/src/pair.rs\nindex 0687073821b93f7fef69c453f8f921ed5ddfca64..43f780ba6ea6ce1cdc2e1f4cbb252ba8a10684b9 100644\n--- a/server/src/pair.rs\n+++ b/server/src/pair.rs\n@@ -1,7 +1,7 @@\n //! Pick two children of a parent scope for pairwise voting.\n //!\n //! Before the pool is one connected voted component, prefer an unvoted edge from\n-//! a never-voted child into an established group (spanning tree growth).\n+//! a never-voted child into a random member of the largest voted group.\n //!\n //! Once connected, run rank centrality once and **zip** adjacent ranks (1 vs 2,\n //! 2 vs 3, …), skipping pairs that already have a vote.\n@@ -101,6 +101,26 @@ fn item_in_established(layout: &ComponentLayout, item: &ItemId) -> bool {\n .is_some_and(|id| layout.established.contains(id))\n }\n \n+/// Established (multi-node voted) components among pool children, largest first.\n+fn established_groups_in_pool<'a>(\n+ layout: &ComponentLayout,\n+ pool: &'a [ItemId],\n+) -> Vec> {\n+ let mut by_comp: HashMap> = HashMap::new();\n+ for item in pool {\n+ if !item_in_established(layout, item) {\n+ continue;\n+ }\n+ let Some(&cid) = layout.ids.get(item) else {\n+ continue;\n+ };\n+ by_comp.entry(cid).or_default().push(item);\n+ }\n+ let mut groups: Vec> = by_comp.into_values().collect();\n+ groups.sort_by_key(|g| std::cmp::Reverse(g.len()));\n+ groups\n+}\n+\n fn ranked_pool_order(group: &GroupState, pool: &[ItemId]) -> Vec {\n let pool_set: HashSet<_> = pool.iter().collect();\n ranked_items(group)\n@@ -138,34 +158,43 @@ fn suggest_grow_pair(\n layout: &ComponentLayout,\n exclude: Option<(&ItemId, &ItemId)>,\n ) -> Option<(ItemId, ItemId)> {\n- let established: Vec<&ItemId> = pool\n- .iter()\n- .filter(|item| item_in_established(layout, item))\n- .collect();\n- let isolates: Vec<&ItemId> = pool\n+ let mut rng = rand::thread_rng();\n+ let groups = established_groups_in_pool(layout, pool);\n+ let mut isolates: Vec<&ItemId> = pool\n .iter()\n .filter(|item| !item_in_established(layout, item))\n .collect();\n+ isolates.shuffle(&mut rng);\n \n- // Attach a never-voted child to the established mass.\n+ // Attach a never-voted child to a random member of the largest voted group.\n for iso in &isolates {\n- for est in &established {\n- if !pair_is_voted(group, iso, est) && !pair_excluded(iso, est, exclude) {\n- return Some(((*iso).clone(), (*est).clone()));\n+ for comp in &groups {\n+ let candidates: Vec<&ItemId> = comp\n+ .iter()\n+ .copied()\n+ .filter(|est| {\n+ !pair_is_voted(group, iso, est) && !pair_excluded(iso, est, exclude)\n+ })\n+ .collect();\n+ if let Some(&est) = candidates.choose(&mut rng) {\n+ return Some(((*iso).clone(), est.clone()));\n }\n }\n }\n \n- // Bridge two established components.\n- for i in 0..established.len() {\n- for j in (i + 1)..established.len() {\n- let a = established[i];\n- let b = established[j];\n- if layout.ids.get(a) == layout.ids.get(b) {\n- continue;\n+ // Bridge two established components (random endpoints, larger groups first).\n+ for i in 0..groups.len() {\n+ for j in (i + 1)..groups.len() {\n+ let mut pairs: Vec<(&ItemId, &ItemId)> = Vec::new();\n+ for a in &groups[i] {\n+ for b in &groups[j] {\n+ if !pair_is_voted(group, a, b) && !pair_excluded(a, b, exclude) {\n+ pairs.push((a, b));\n+ }\n+ }\n }\n- if !pair_is_voted(group, a, b) && !pair_excluded(a, b, exclude) {\n- return Some((a.clone(), b.clone()));\n+ if let Some((a, b)) = pairs.choose(&mut rng) {\n+ return Some(((*a).clone(), (*b).clone()));\n }\n }\n }\n","role":"user"}],"model":"~anthropic/claude-sonnet-latest"}