constitution · epochs · watch · epoch 3

comparison

c_ef71be9831cc (tommy-mor) vs c_48fcbcde8f88 (tommy-mor)

download prompt · raw event · cmp_8cd54c63769213

council reasoning

~anthropic/claude-sonnet-latest · winner A · 8:2 · permalink

Side A performs a substantive architectural refactor of the ranking data model (replacing cached GroupState with derived-on-demand ScopeVotes), removing stale cached invariants and simplifying storage/reducer logic while preserving/updating test coverage across many files. Side B is pure test-infrastructure deduplication (moving duplicated bb helper functions into shared modules) which improves maintainability but has no functional or architectural impact on the actual product.

~x-ai/grok-latest · winner A · 5:1 · permalink

Commit A redesigns core ranking state (GroupState → ScopeVotes), dropping cached edges/indexes and rebuilding weights/components only at rank time across reducer, ranking, storage, and UI paths—a lasting correctness and simplicity win. Commit B only extracts duplicated babashka test helpers (HTTP, mock OAuth, asserts, cargo env) into shared modules with no production behavior change.

openai/gpt-chat-latest · winner A · 10:1 · permalink

Side A fundamentally redesigns ranking storage by replacing cached `GroupState` edge/index data with a minimal `ScopeVotes` model and rebuilding edges, item indexes, connected components, and rankings on demand. This touches ranking, persistence, reducers, UI, and tests to preserve behavior while simplifying state and eliminating duplicated cached data, whereas Side B mainly consolidates duplicated integration-test helpers into shared utilities (`test.common`/`test.oauth`) without changing core project behavior.

sides

A — c_ef71be9831cc (tommy-mor)

message

[af73743d] Replace GroupState with ScopeVotes and derive edges at ranking time.

Store only uuid_votes and recent_votes per scope; rank centrality and pair logic rebuild edge weights on demand instead of maintaining cached state.

Co-authored-by: Cursor <cursoragent@cursor.com>

diff preview

diff --git a/server/src/events.rs b/server/src/events.rs
index 8a166d49b4f26835fbc2b58cb1f4bdbf002763b8..015208311f6c5c23a0e8aab068d002a69c89c4e1 100644
--- a/server/src/events.rs
+++ b/server/src/events.rs
@@ -43,7 +43,7 @@ pub enum ViewEvent {
 #[derive(Debug, Clone, Serialize, Deserialize)]
 #[serde(tag = "type", rename_all = "snake_case")]
 pub enum Event {
-    /// Pairwise comparison vote (replayed into the parent node's [`crate::reducer::GroupState`] on boot).
+    /// Pairwise comparison vote (replayed into the parent node's [`crate::reducer::ScopeVotes`] on boot).
     /// `scope` is the parent [`crate::path_types::ItemId`] string; empty string is the tree root.
     VoteRecorded {
         ts: i64,
diff --git a/server/src/html/mod.rs b/server/src/html/mod.rs
index 4eff2e19ed4d303ff8e80c1eabd8a15b4990e643..1e2e7a06856d8a62378741aaf5ed94a4ffed337e 100644
--- a/server/src/html/mod.rs
+++ b/server/src/html/mod.rs
@@ -13,7 +13,7 @@ use crate::{
     form_template::template_json_compact,
     path_types::ItemId,
     ranking::{
-        connected_components_from_voted_pairs, ranked_items_subset, RankedItem, MAX_ITERS, TOL,
+        ranked_items_subset, scope_components, RankedItem, MAX_ITERS, TOL,
     },
     reducer::{GlobalTree, NodeState},
     state::AppState,
@@ -397,10 +397,9 @@ pub fn ranking_panel_with_highlights(
     tree: &GlobalTree,
     highlighted: &HashSet<ItemId>,
 ) -> Markup {
-    let group = &node.local_ranking;
-    let n = group.idx_to_item.len();
-    let (comps, _isolates) =
-        connected_components_from_voted_pairs(n, group.voted_pairs.iter().copied());
+    let scope = &node.votes;
+    let (comps, _isolates, _) =
+        scope_components(scope);
 
     // Each connected component of voted items is its own ranking; isolated and
     // never-voted children fall into the "unranked" bucket below.
@@ -410,7 +409,7 @@ pub fn ranking_panel_with_highlights(
         if comp.len() < 2 {
             continue;
         }
-        let ranked = ranked_items_subset(group, comp, MAX_ITERS, TOL);
+        let ranked = ranked_items_subset(scope, comp, MAX_ITERS, TOL);
         for r in &ranked {
             ranked_ids.insert(r.item.clone());
         }
diff --git a/server/src/html/vote.rs b/server/src/html/vote.rs
index bf82aef3ad9c5f4e9e877c47dab27beb29a80b8f..3aa00c417c89a9cab3417c650b50ed7c73f08e20 100644
--- a/server/src/html/vote.rs
+++ b/server/src/html/vote.rs
@@ -14,7 +14,7 @@ use crate::{
     html::{ranking_panel_with_highlights, scope_theme_style, JsBuilder},
     pair::{children_of, resolve_pair, suggest_next_pair_in_pool},
     path_types::ItemId,
-    reducer::{GlobalTree, GroupState, NodeState, VoteData},
+    reducer::{GlobalTree, NodeState, ScopeVotes, VoteData},
     state::{parse_item_param, AppState},
     ui_action::UI_RPC_FIELD,
 };
@@ -68,8 +68,8 @@ fn ratios_for_page(v: &VoteData, page_left: &ItemId, page_right: &ItemId) -> (i3
     }
 }
 
-fn edge_votes(group: &GroupState, left: &ItemId, right: &ItemId) -> Vec<VoteData> {
-    group
+fn edge_votes(scope: &ScopeVotes, left: &ItemId, right: &ItemId) -> Vec<VoteData> {
+    scope
         .recent_votes
         .iter()
         .filter(|v| {
@@ -113,11 +113,11 @@ fn slider_value_from_ratios(r_left: i32, r_right: i32) -> i32 {
 
 fn vote_edge_history(
     tree: &GlobalTree,
-    group: &GroupState,
+    scope: &ScopeVotes,
     left: &ItemId,
     right: &ItemId,
 ) -> Markup {
-    let mut votes = edge_votes(group, left, right);
+    let mut votes = edge_votes(scope, left, right);
     votes.sort_by(|a, b| b.ts.cmp(&a.ts));
     let legend_left = child_title(tree, left);
     let legend_right = child_title(tree, right);
@@ -228,9 +228,9 @@ pub(crate) fn vote_recorded_morph(
 ) -> JsBuilder {
     let pool = children_of(tree, parent);
     let empty = NodeState::default();
-    let group = tree.get(parent).unwrap_or(&empty).local_ranking.clone();
-    let edge_history = vote_edge_history(tree, &group, left, right);
-    let next_pair = suggest_next(&group, left, right, &pool);
+    let scope = tree.get(parent).unwrap_or(&empty).votes.clone();
+    let edge_history = vote_edge_history(tree, &scope, left, right);
+    let next_pair = suggest_next(&scope, left, right, &pool);
     let actions = vote_compare_actions(parent, next_pair.as_ref());
     let sidebar = vote_ranking_sidebar(tree, parent, left, right);
     JsBuilder::new()
@@ -252,12 +252,12 @@ fn vote_compare_item_card(tree: &GlobalTree, item: &ItemId, side_class: &str) ->
 }
 
 fn suggest_next(
-    group: &GroupState,
+    scope: &ScopeVotes,
     left: &ItemId,
     right: &ItemId,
     pool: &[ItemId],
 ) -> Option<(ItemId, ItemId)> {
-    suggest_next_pair_in_pool(group, pool, Some((left, right)))
+    suggest_next_pair_in_pool(scope, pool, Some((left, right)))
 }
 
 pub async fn vote_page(
@@ -284,9 +284,9 @@ pub async fn vote_page(
         };
 
     let pool = children_of(&tree, &parent);
-    let group = &parent_node.local_ranking;
-    let next_pair = suggest_next(group, &left, &right, &pool);
-    let edge_history = vote_edge_history(&tree, group, &left, &right);
+    let scope = &parent_node.votes;
+    let next_pair = suggest_next(&scope, &left, &right, &pool);
+    let edge_history = vote_edge_history(&tree, &scope, &left, &right);
 
     let rpc_json = template_json_compact(&serde_json::json!({
         "action": "record_vote",
@@ -388,8 +388,8 @@ mod polarity_tests {
         let mut tree = GlobalTree::new();
         tree.apply_vote(&parent, vote, TEST_ACTOR_UUID);
 
-        let group = &tree.get(&parent).unwrap().local_ranking;
-        let ranked = ranked_items(group);
+        let scope = &tree.get(&parent).unwrap().votes;
+        let ranked = ranked_items(scope);
         assert_eq!(
             ranked[0].item, left,
             "left item should rank first when ratio favours the left"
diff --git a/server/src/pair.rs b/server/src/pair.rs
index 42a1b1eb2adf16730d34d0fe23c13d5a75d7ba27..9873295c51526726089875bbfd2f97d7faa91872 100644
--- a/server/src/pair.rs
+++ b/server/src/pair.rs
@@ -11,8 +11,8 @@ use std::collections::{HashMap, HashSet};
 
 use crate::{
     path_types::ItemId,
-    ranking::{connected_components_from_voted_pairs, ranked_items},
-    reducer::{GlobalTree, GroupState},
+    ranking::{pair_is_voted, ranked_items, scope_components},
+    reducer::{GlobalTree, ScopeVotes},
 };
 
 fn pairs_match(a: &ItemId, b: &ItemId, x: &ItemId, y: &ItemId) -> bool {
@@ -23,26 +23,15 @@ fn pair_excluded(a: &ItemId, b: &ItemId, exclude: Option<(&ItemId, &ItemId)>) ->
     exclude.is_some_and(|(x, y)| pairs_match(a, b, x, y))
 }
 
-fn pair_is_voted(group: &GroupState, a: &ItemId, b: &ItemId) -> bool {
-    let Some(&ai) = group.item_to_idx.get(a) else {
-        return false;
-    };
-    let Some(&bi) = group.item_to_idx.get(b) else {
-        return false;
-    };
-    let (i, j) = if ai < bi { (ai, bi) } else { (bi, ai) };
-    group.voted_pairs.contains(&(i, j))
-}
 
 struct ComponentLayout {
     ids: HashMap<ItemId, usize>,
     established: HashSet<usize>,
 }
 
-fn component_layout(group: &GroupState, pool: &[ItemId]) -> ComponentLayout {
-    let n = group.idx_to_item.len();
-    let (comps, isolates) =
-        connected_components_from_voted_pairs(n, group.voted_pairs.iter().copied());
+fn component_layout(scope: &ScopeVotes, pool: &[ItemId]) -> ComponentLayout {
+    let (comps, isolates, idx_to_item) = scope_components(scope);
+    let n = idx_to_item.len();
 
     let mut established = HashSet::new();
     let mut ids: HashMap<ItemId, usize> = HashMap::new();
@@ -52,14 +41,14 @@ fn component_layout(group: &GroupState, pool: &[ItemId]) -> ComponentLayout {
         }
         for &idx in comp {
             if idx < n {
-                ids.insert(group.idx_to_item[idx].clone(), comp_idx);
+                ids.insert(idx_to_item[idx].clone(), comp_idx);
             }
         }
     }
     let mut next = comps.len();
     for &idx in &isolates {
         if idx < n {
-            ids.insert(group.idx_to_item[idx].clone(), next);
+            ids.insert(idx_to_item[idx].clone(), next);
             next += 1;
         }
     }
@@ -121,7 +110,7 @@ fn established_groups_in_pool<'a>(
     groups
 }
 
-fn ranked_pool_order(group: &GroupState, pool: &[ItemId]) -> Vec<ItemId> {
+fn ranked_pool_order(group: &ScopeVotes, pool: &[ItemId]) -> Vec<ItemId> {
     let pool_set: HashSet<_> = pool.iter().collect();
     ranked_items(group)
         .into_iter()
@@ -132,7 +121,7 @@ fn ranked_pool_order(group: &GroupState, pool: &[ItemId]) -> Vec<ItemId> {
 
 /// Walk 1↔2, 2↔3, …; optional `require_unvoted` skips voted edges.
 fn zip_adjacent_pair(
-    group: &GroupState,
+    group: &ScopeVotes,
     order: &[ItemId],
     exclude: Option<(&ItemId, &ItemId)>,
     require_unvoted: bool,
@@ -153,7 +142,7 @@ fn zip_adjacent_pair(
 
 /// Grow the voted graph toward one component (no rank centrality).
 fn suggest_grow_pair(
-    group: &GroupState,
+    group: &ScopeVotes,
     pool: &[ItemId],
     layout: &ComponentLayout,
     exclude: Option<(&ItemId, &ItemId)>,
@@ -216,7 +205,7 @@ fn suggest_grow_pair(
 
 /// Pick the next pair to vote on within `pool`.
 pub fn suggest_next_pair_in_pool(
-    group: &GroupState,
+    group: &ScopeVotes,
     pool: &[ItemId],
     exclude: Option<(&ItemId, &ItemId)>,
 ) -> Option<(ItemId, ItemId)> {
@@ -315,7 +304,7 @@ pub fn resolve_pair(
         (None, None) => {
             let group = tree
                 .get(parent)
-                .map(|n| &n.local_ranking)
+                .map(|n| &n.votes)
                 .cloned()
                 .unwrap_or_default();
             suggest_next_pair_in_pool(&group, &children, None).ok_or(PairError::NoPair)
@@ -398,7 +387,7 @@ mod tests {
                 "https://reddit.com/r/rust/b",
             ],
         );
-        let group = tree.get(&parent).unwrap().local_ranking.clone();
+        let group = tree.get(&parent).unwrap().votes.clone();
         let pool = children_of(&tree, &parent);
         assert!(!pair_is_voted(&group, &pool[0], &pool[1]));
         assert!(suggest_next_pair_in_pool(&group, &pool, None).is_some());
@@ -417,7 +406,7 @@ mod tests {
         );
         let vote = test_vote(1, "https://reddit.com/r/rust/a", "https://reddit.com/r/rust/b", 2, 1);
         apply(&mut tree, &parent, vote);
-        let group = tree.get(&parent).unwrap().local_ranking.clone();
+        let group = tree.get(&parent).unwrap().votes.clone();
         let pool = children_of(&tree, &parent);
         let (l, r) = suggest_next_pair_in_pool(&group, &pool, None).unwrap();
         let voted_ab = (l.as_str() == "https://reddit.com/r/rust/a" && r.as_str() == "https://reddit.com/r/rust/b")
@@ -441,7 +430,7 @@ mod tests {
         let cd = test_vote(2, "https://reddit.com/r/rust/c", "https://reddit.com/r/rust/d", 2, 1);
         apply(&mut tree, &parent, ab);
         apply(&mut tree, &parent, cd);
-        let group = tree.get(&parent).unwrap().local_ranking.clone();
+        let group = tree.get(&parent).unwrap().votes.clone();
         let pool = children_of(&tree, &parent);
         let pair = suggest_next_pair_in_pool(&group, &pool, None).unwrap();
         let chosen = pair_set(&pair);
@@ -467,7 +456,7 @@ mod tests {
         );
         let ab = test_vote(1, "https://reddit.com/r/rust/a", "https://reddit.com/r/rust/b", 2, 1);
         apply(&mut tree, &parent, ab);
-        let group = tree.get(&parent).unwrap().local_ranking.clone();
+        let group = tree.get(&parent).unwrap().votes.clone();
         let pool = children_of(&tree, &parent);
         let pair = suggest_next_pair_in_pool(&group, &pool, None).unwrap();
         let chosen = pair_set(&pair);
@@ -496,7 +485,7 @@ mod tests {
         );
         let ab = test_vote(1, "https://reddit.com/r/rust/a", "https://reddit.com/r/rust/b", 2, 1);
         apply(&mut tree, &parent, ab);
-        let group = tree.get(&parent).unwrap().local_ranking.clone();
+       

… preview truncated; 38,568 characters omitted

download full diff A

B — c_48fcbcde8f88 (tommy-mor)

message

[b32c92f2] consolidated shared functions in tests (composer 2 fast)

diff preview

diff --git a/test/auth.bb b/test/auth.bb
index a67cc1de763434176d2f68e419dd37a8cd328395..78cffc875ffe83f512bca253f4a7489227311da4 100644
--- a/test/auth.bb
+++ b/test/auth.bb
@@ -3,113 +3,14 @@
   (:require [babashka.process :as p]
             [babashka.fs :as fs]
             [cheshire.core :as json]
-            [org.httpkit.server :as http]
-            [test.common :as common]))
+            [clojure.string :as str]
+            [test.common :as common]
+            [test.oauth :as oauth]))
 
-(def ^:private ansi-green "\033[32m")
-(def ^:private ansi-red   "\033[31m")
-(def ^:private ansi-reset "\033[0m")
 (def ^:private counts (atom {:pass 0 :fail 0}))
 
-(defn- pass [msg]
-  (swap! counts update :pass inc)
-  (println (str ansi-green "  ✓ " ansi-reset msg)))
-
-(defn- fail [msg]
-  (swap! counts update :fail inc)
-  (println (str ansi-red "  ✗ " ansi-reset msg)))
-
 (defn- assert! [pred msg]
-  (if pred
-    (pass msg)
-    (do (fail msg)
-        (throw (ex-info (str "FAIL: " msg) {})))))
-
-(defn- http-client []
-  (-> (java.net.http.HttpClient/newBuilder)
-      (.followRedirects java.net.http.HttpClient$Redirect/ALWAYS)
-      (.build)))
-
-(defn- http-get [url & {:keys [headers]}]
-  (let [b (java.net.http.HttpRequest/newBuilder (java.net.URI/create url))]
-    (doseq [[k v] (or headers {})]
-      (.header b k v))
-    (let [req (-> b (.GET) (.build))
-          resp (.send (http-client) req (java.net.http.HttpResponse$BodyHandlers/ofString))]
-      {:status (.statusCode resp) :body (.body resp) :headers (.map (.headers resp))})))
-
-(defn- http-post-json [url data & {:keys [headers]}]
-  (let [body (json/generate-string data)
-        b (java.net.http.HttpRequest/newBuilder (java.net.URI/create url))]
-    (.header b "Content-Type" "application/json")
-    (doseq [[k v] (or headers {})]
-      (.header b k v))
-    (let [req (-> b
-                  (.POST (java.net.http.HttpRequest$BodyPublishers/ofString body))
-                  (.build))
-          resp (.send (http-client) req (java.net.http.HttpResponse$BodyHandlers/ofString))]
-      {:status (.statusCode resp) :body (.body resp) :headers (.map (.headers resp))})))
-
-(defn- http-post-form [url form]
-  (let [pairs (->> form
-                   (map (fn [[k v]]
-                          (str (java.net.URLEncoder/encode (name k) "UTF-8")
-                               "="
-                               (java.net.URLEncoder/encode (str v) "UTF-8"))))
-                   (clojure.string/join "&"))
-        b (java.net.http.HttpRequest/newBuilder (java.net.URI/create url))]
-    (.header b "Content-Type" "application/x-www-form-urlencoded")
-    (let [req (-> b
-                  (.POST (java.net.http.HttpRequest$BodyPublishers/ofString pairs))
-                  (.build))
-          resp (.send (http-client) req (java.net.http.HttpResponse$BodyHandlers/ofString))]
-      {:status (.statusCode resp) :body (.body resp) :headers (.map (.headers resp))})))
-
-(defn- parse-query [s]
-  (into {}
-        (for [part (clojure.string/split (or s "") #"&")
-              :when (not (clojure.string/blank? part))]
-          (let [[k v] (clojure.string/split part #"=" 2)]
-            [(keyword (java.net.URLDecoder/decode k "UTF-8"))
-             (some-> v (java.net.URLDecoder/decode "UTF-8"))]))))
-
-(defn- b64url [s]
-  (-> (java.util.Base64/getUrlEncoder)
-      (.withoutPadding)
-      (.encodeToString (.getBytes s "UTF-8"))))
-
-(defn- make-id-token [sub]
-  (str (b64url "{\"alg\":\"RS256\",\"typ\":\"JWT\"}")
-       "."
-       (b64url (json/generate-string {:sub sub}))
-       ".fakesig"))
-
-(defn- start-mock-google [port]
-  (let [google-users ["google-user-1" "google-user-2"]
-        !call-count (atom 0)
-        handler
-        (fn [req]
-          (cond
-            (and (= :get (:request-method req))
-                 (= "/o/oauth2/v2/auth" (:uri req)))
-            (let [q (parse-query (:query-string req))
-                  redirect-uri (:redirect_uri q)
-                  state (:state q)
-                  loc (str redirect-uri "?code=mockcode&state=" state)]
-              {:status 302 :headers {"Location" loc} :body ""})
-
-            (and (= :post (:request-method req))
-                 (= "/token" (:uri req)))
-            (let [n   (swap! !call-count inc)
-                  sub (nth google-users (mod (dec n) (count google-users)))]
-              {:status 200
-               :headers {"Content-Type" "application/json"}
-               :body (json/generate-string {:id_token (make-id-token sub)})})
-
-            :else
-            {:status 404 :body "not found"}))
-        stop-fn (http/run-server handler {:port port})]
-    {:stop-fn stop-fn :port port}))
+  (common/test-assert! counts pred msg))
 
 (defn auth-test [& _args]
   (println "\n━━━ auth v3 integration check ━━━\n")
@@ -117,8 +18,7 @@
 
   (println "building server + CLI binaries…")
   (common/letlocals
-   (bind build @(p/process [(common/cargo-bin) "build" "--release" "-p" "slugsocial-server" "-p" "slugsocial"]
-                           {:inherit true :env common/base-env}))
+   (bind build (common/run-cargo-build-release! ["slugsocial-server" "slugsocial"]))
    (assert! (zero? (:exit build)) "cargo build succeeds")
    (bind server-bin "target/release/slugsocial-server")
    (bind cli-bin "target/release/slugsocial")
@@ -134,19 +34,10 @@
    (bind !server (atom nil))
    (bind !google (atom nil))
 
-   (bind server-env (merge common/base-env
-                           {"SLUG_DATA_DIR" tmp-dir
-                            "SLUG_KEYS"     "test:test"
-                            "PORT"          (str slug-port)
-                            "RUST_LOG"      "warn"
-                            "SLUG_PUBLIC_URL" base-url
-                            "SLUG_GOOGLE_AUTH_URL" (str google-url "/o/oauth2/v2/auth")
-                            "SLUG_GOOGLE_TOKEN_URL" (str google-url "/token")
-                            "SLUG_GOOGLE_CLIENT_ID" "mock"
-                            "SLUG_GOOGLE_CLIENT_SECRET" "mock"}))
+   (bind server-env (common/slug-server-env tmp-dir base-url google-url slug-port))
    (try
      (println (str "\nstarting mock google on :" google-port))
-     (reset! !google (start-mock-google google-port))
+     (reset! !google (oauth/start-mock-google google-port :google-users ["google-user-1" "google-user-2"]))
      (assert! (some? (:stop-fn @!google)) "mock google started")
 
      (println (str "starting server on :" slug-port))
@@ -154,34 +45,33 @@
      (assert! (common/wait-for-server base-url 10000) "server responds to /healthz")
 
      (println "\nstarting pending session…")
-     (let [start-resp (http-post-json (str base-url "/api/v0/pending-session")
-                                      {:agent "00000000-0000-0000-0000-000000000000:bb:local/dev"})
+     (let [start-resp (oauth/http-post-json (str base-url "/api/v0/pending-session")
+                                            {:agent "00000000-0000-0000-0000-000000000000:bb:local/dev"})
            _ (assert! (= 200 (:status start-resp)) "pending-session start returns 200")
            start-json (json/parse-string (:body start-resp) true)]
-       (assert! (clojure.string/starts-with? (:session start-json) "p_") "session id has p_ prefix")
-       (assert! (clojure.string/includes? (:login_url start-json) "/auth/login") "login_url provided")
+       (assert! (str/starts-with? (:session start-json) "p_") "session id has p_ prefix")
+       (assert! (str/includes? (:login_url start-json) "/auth/login") "login_url provided")
 
        (println "\nsimulating browser oauth redirects…")
-       ;; This will follow redirects: /auth/login -> mock google -> /auth/callback -> /auth/choose-username
-       (let [login-get (http-get (:login_url start-json))]
+       (let [login-get (oauth/http-get (:login_url start-json))]
          (assert! (= 200 (:status login-get)) "choose-username page reachable after oauth callback"))
 
        (println "\nchoosing username…")
-       (let [choose (http-post-form (str base-url "/auth/choose-username")
-                                    {:session (:session start-json) :username "bbuser"})]
+       (let [choose (oauth/http-post-form (str base-url "/auth/choose-username")
+                                          {:session (:session start-json) :username "bbuser"})]
          (assert! (= 200 (:status choose)) "choose-username POST returns 200"))
 
        (println "\npolling pending session…")
-       (let [poll (http-get (str base-url "/api/v0/pending-session/" (:session start-json)))]
+       (let [poll (oauth/http-get (str base-url "/api/v0/pending-session/" (:session start-json)))]
          (assert! (= 200 (:status poll)) "pending-session poll returns 200")
          (let [poll-json (json/parse-string (:body poll) true)]
            (assert! (:complete poll-json) "pending session complete=true")
            (assert! (= "bbuser" (:user poll-json)) "poll returns stored username bbuser")
-           (assert! (clojure.string/starts-with? (:token poll-json) "slug_") "poll returns bearer token")
+           (assert! (str/starts-with? (:token poll-json) "slug_") "poll returns bearer token")
 
            (println "\nwhoami…")
-           (let [who (http-get (str base-url "/api/v0/whoami")
-                               :headers {"Authorization" (str "Bearer " (:token poll-json))})]
+           (let [who (oauth/http-get (str base-url "/api/v0/whoami")
+                                     :headers {"Authorization" (str "Bearer " (:token poll-json))})]
              (assert! (= 200 (:status who)) "whoami returns 200")
              (let [who-json (json/parse-string (:body who) true)]
                (assert! (= "bbuser" (:user who-json)) "whoami user is bbuser (stored form)"))))))
@@ -195,11 +85,11 @@
                   (str "identity start exits 0 (stderr: " (:err start-proc) ")"))
          (let [start-cli (json/parse-string (:out start-proc) true)]
            (assert! (= "present_oauth_url_to_user" (:phase start-cli)) "identity start --json phase")
-           (assert! (clojure.string/starts-with? (:session start-cli) "p_") "CLI start session id")
-           (let [login-get (http-get (:login_url start-cli))]
+           (assert! (str/starts-with? (:session start-cli) "p_") "CLI start session id")
+           (let [login-get (oauth/http-get (:login_url start-cli))]
              (assert! (= 200 (:status login-get)) "CLI login_url redirect chain succeeds"))
-           (let [choose (http-post-form (str base-url "/auth/choose-username")
-                                        {:session (:session start-cli) :username "cliuser"})]
+           (let [choose (oauth/http-post-form (str base-url "/auth/choose-username")
+                                              {:session (:session start-cli) :username "cliuser"})]
              (assert! (= 200 (:status choose)) "choose-username for cliuser"))
            (let [poll-proc @(p/process [cli-bin "identity" "poll" (:session start-cli)
                                         "--poll-interval-ms" "100" "--max-wait-secs" "30" "--json"]
@@ -209,10 +99,10 @@
              (let [poll-cli (json/parse-string (:out poll-proc) true)]
                (assert! (= "complete" (:phase poll-cli)) "identity poll --json phase")
                (assert! (= "cliuser" (:user poll-cli)) "CLI poll user (stored form)")
-               (assert! (clojure.string/starts-with? (:token poll-cli) "slug_") "CLI poll token")
+               (assert! (str/starts-with? (:token poll-cli) "slug_") "CLI poll token")
                (let [token-path (str cli-home "/.config/slugsocial/token")]
                  (assert! (fs/exists? token-path) "token written under isolated HOME")
-                 (assert! (= (clojure.string/trim (slurp token-path)) (:token poll-cli))
+                 (assert! (= (str/trim (slurp token-path)) (:token poll-cli))
                           "token file matches poll JSON"))
      

… preview truncated; 24,704 characters omitted

download full diff B

Hardlinks — judgments / attempts / prompt

prompt download

judgments

attempts

Prompt text is loaded only by the download route.