constitution · epochs · watch · epoch 3

comparison

c_11ce057e37af (tommy-mor) vs c_7a129e904906 (tommy-mor)

download prompt · raw event · cmp_f4d8aa76dd1d37

council reasoning

~anthropic/claude-sonnet-latest · winner B · 6:4 · permalink

Side B ships a coherent, tested feature set (deploy pipeline, Dockerfile, fly.toml, GitHub auth for git mirroring, a live /watch dashboard with SSE audit events, and single-contributor fast-path fix for rank_commits) backed by unit tests and integration test updates, giving durable infrastructure and correctness value. Side A is a solid, well-tested parser improvement (deterministic tokens, prose URL linkification, braced-body enforcement) but is narrower in scope and mostly internal refactoring/feature polish within one module.

~x-ai/grok-latest · winner B · 2:1 · permalink

B delivers lasting operational value: production deploy (Docker/Fly/CI), multi-repo contributor config, emission retry, and a real audited /watch SSE progress surface with status API and tests—foundational for running the constitution itself. A is strong design (deterministic typed BlockMasker, prose ref tokenizer, braced body rules, linkify), but it is product/DSL polish plus some formatting churn, not the same system-level necessity.

openai/gpt-chat-latest · winner A · 3:2 · permalink

Side A makes substantive parser and rendering improvements: it introduces typed deterministic block masking, a prose item-reference tokenizer that correctly handles raw URLs, punctuation, newlines, and code fences, enforces braced DSL item bodies, updates linkification accordingly, and adds focused tests for these behaviors. Side B adds valuable deployment infrastructure and a live audit dashboard (Docker, CI/CD, SSE status/UI, production config), but much of its impact is operational rather than improving the project's core parsing and data model, so its lasting contribution is somewhat smaller.

sides

A — c_11ce057e37af (tommy-mor)

message

[08565bea] Tokenize prose refs for garden URL links (#147)

* Tokenize prose refs for garden URL links

Co-authored-by: tommy <thmorriss@gmail.com>

* Stop prose URLs at line boundaries

Co-authored-by: tommy <thmorriss@gmail.com>

* Require braced DSL item bodies

Co-authored-by: tommy <thmorriss@gmail.com>

---------

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

diff preview

diff --git a/server/src/dsl.rs b/server/src/dsl.rs
index 4203c2f59dd8825d7a91513c4023f3ccd37f1efc..b3c785674560a0b42a7f4c3aa13d80cd350f485b 100644
--- a/server/src/dsl.rs
+++ b/server/src/dsl.rs
@@ -1,7 +1,5 @@
 use std::collections::HashMap;
 
-use rand::Rng;
-
 /// Parsed DSL document.
 #[derive(Debug, Clone, PartialEq, Eq)]
 pub struct Document {
@@ -39,31 +37,57 @@ pub enum DslError {
 /// Matches the legacy Python parser behavior:
 /// - Supports toggle markers (open == close), e.g. ```...```
 /// - Supports nested markers (open != close), e.g. { ... { ... } ... }
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum BlockKind {
+    CodeFence,
+    DoubleBrace,
+    Brace,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+struct MaskedBlock {
+    kind: BlockKind,
+}
+
 #[derive(Debug, Default, Clone)]
 pub struct BlockMasker {
     pub replacements: HashMap<String, String>,
+    blocks: HashMap<String, MaskedBlock>,
+    next_id: u32,
 }
 
 impl BlockMasker {
     pub fn new() -> Self {
         Self {
             replacements: HashMap::new(),
+            blocks: HashMap::new(),
+            next_id: 0,
         }
     }
 
-    fn new_token(&mut self) -> String {
-        let mut rng = rand::thread_rng();
-        let n: u32 = rng.gen();
-        let token = format!("__BLOCK_{:08x}__", n);
-        // Extremely unlikely collision; if it happens, regenerate.
-        if self.replacements.contains_key(&token) {
-            return self.new_token();
+    fn new_token(&mut self, haystack: &str) -> String {
+        loop {
+            let token = format!("__BLOCK_{:08x}__", self.next_id);
+            self.next_id = self.next_id.wrapping_add(1);
+            if !self.replacements.contains_key(&token) && !haystack.contains(&token) {
+                return token;
+            }
         }
-        token
     }
 
     /// Replace outermost balanced blocks with tokens.
     pub fn mask(&mut self, text: &str, open_marker: &str, close_marker: &str) -> String {
+        self.mask_kind(text, open_marker, close_marker, BlockKind::Brace)
+    }
+
+    /// Replace outermost balanced blocks with typed deterministic tokens.
+    pub fn mask_kind(
+        &mut self,
+        text: &str,
+        open_marker: &str,
+        close_marker: &str,
+        kind: BlockKind,
+    ) -> String {
         if text.is_empty() {
             return text.to_string();
         }
@@ -97,9 +121,10 @@ impl BlockMasker {
                     // Found end of outermost block
                     let s = start_idx.max(0) as usize;
                     let original_block = &text[s..i];
-                    let token = self.new_token();
+                    let token = self.new_token(text);
                     self.replacements
                         .insert(token.clone(), original_block.to_string());
+                    self.blocks.insert(token.clone(), MaskedBlock { kind });
                     result_parts.push(token);
                     current_idx = i;
                 }
@@ -176,13 +201,22 @@ impl BlockMasker {
         }
         token.to_string()
     }
+
+    pub fn block_kind(&self, token: &str) -> Option<BlockKind> {
+        self.blocks.get(token).map(|b| b.kind)
+    }
 }
 
 fn mask_all(mut masker: BlockMasker, text: &str) -> (BlockMasker, String) {
     // Mask hierarchy: Code -> Double Brace -> Single Brace.
-    let t = masker.mask(text, "```", "```");
-    let t = masker.mask(&t, "{{", "}}");
-    let t = masker.mask(&t, "{", "}");
+    let t = masker.mask_kind(text, "```", "```", BlockKind::CodeFence);
+    let t = masker.mask_kind(&t, "{{", "}}", BlockKind::DoubleBrace);
+    let t = masker.mask_kind(&t, "{", "}", BlockKind::Brace);
+    (masker, t)
+}
+
+fn mask_code_fences(mut masker: BlockMasker, text: &str) -> (BlockMasker, String) {
+    let t = masker.mask_kind(text, "```", "```", BlockKind::CodeFence);
     (masker, t)
 }
 
@@ -253,7 +287,34 @@ fn skip_ws(s: &str, mut i: usize) -> usize {
     i
 }
 
-fn parse_item_name_at(s: &str, i: usize) -> Option<(String, usize)> {
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum ProseToken {
+    Text(String),
+    ItemRef(String),
+}
+
+fn trim_prose_item_ref_end(s: &str, mut end: usize) -> usize {
+    while end > 0 {
+        let Some((idx, c)) = s[..end].char_indices().next_back() else {
+            break;
+        };
+        if matches!(
+            c,
+            '.' | ',' | ';' | ':' | '!' | '?' | ')' | ']' | '}' | '"' | '\''
+        ) {
+            end = idx;
+        } else {
+            break;
+        }
+    }
+    end
+}
+
+fn parse_item_name_at_with_mode(
+    s: &str,
+    i: usize,
+    trim_trailing_punctuation: bool,
+) -> Option<(String, usize)> {
     let bytes = s.as_bytes();
     if i >= bytes.len() {
         return None;
@@ -263,6 +324,9 @@ fn parse_item_name_at(s: &str, i: usize) -> Option<(String, usize)> {
     if s[i..].starts_with("https://") || s[i..].starts_with("http://") {
         let mut j = i;
         while j < bytes.len() {
+            if trim_trailing_punctuation && bytes[j] == b'\n' {
+                break;
+            }
             if bytes[j..].starts_with(b"__BLOCK_") || is_ws_byte(bytes[j]) {
                 break;
             }
@@ -271,6 +335,12 @@ fn parse_item_name_at(s: &str, i: usize) -> Option<(String, usize)> {
         if j <= i {
             return None;
         }
+        if trim_trailing_punctuation {
+            j = trim_prose_item_ref_end(s, j);
+            if j <= i {
+                return None;
+            }
+        }
         return Some((s[i..j].to_string(), j));
     }
 
@@ -296,6 +366,12 @@ fn parse_item_name_at(s: &str, i: usize) -> Option<(String, usize)> {
         if j <= i + 2 {
             return None;
         }
+        if trim_trailing_punctuation {
+            j = trim_prose_item_ref_end(s, j);
+            if j <= i + 2 {
+                return None;
+            }
+        }
         let raw = &s[i..j];
         if !is_item_name(raw) {
             return None;
@@ -336,6 +412,46 @@ fn parse_item_name_at(s: &str, i: usize) -> Option<(String, usize)> {
     Some((format!("~/{}", name), j))
 }
 
+fn parse_item_name_at(s: &str, i: usize) -> Option<(String, usize)> {
+    parse_item_name_at_with_mode(s, i, false)
+}
+
+pub fn parse_prose_item_ref_at(s: &str, i: usize) -> Option<(String, usize)> {
+    parse_item_name_at_with_mode(s, i, true)
+}
+
+pub fn tokenize_prose_item_refs(text: &str) -> Vec<ProseToken> {
+    if text.is_empty() {
+        return Vec::new();
+    }
+    let (masker, masked) = mask_code_fences(BlockMasker::new(), text);
+    let mut tokens = Vec::new();
+    let mut text_start = 0usize;
+    let mut i = 0usize;
+
+    while i < masked.len() {
+        if let Some((raw, end)) = parse_prose_item_ref_at(&masked, i) {
+            if text_start < i {
+                tokens.push(ProseToken::Text(masker.unmask(&masked[text_start..i])));
+            }
+            tokens.push(ProseToken::ItemRef(masker.unmask(&raw)));
+            i = end;
+            text_start = i;
+            continue;
+        }
+
+        let Some((_, c)) = masked[i..].char_indices().next() else {
+            break;
+        };
+        i += c.len_utf8();
+    }
+
+    if text_start < masked.len() {
+        tokens.push(ProseToken::Text(masker.unmask(&masked[text_start..])));
+    }
+    tokens
+}
+
 fn parse_block_token_at(s: &str, i: usize) -> Option<(String, usize)> {
     let bytes = s.as_bytes();
     if i >= bytes.len() {
@@ -401,6 +517,12 @@ fn parse_block_prefixed_statement(
     tail: &str,
     masker: &BlockMasker,
 ) -> Result<Stmt, DslError> {
+    if masker.block_kind(block_token) == Some(BlockKind::CodeFence) {
+        return Err(DslError::Parse(
+            "vote explanations must use `{ ... }`; code fences belong inside body blocks"
+                .to_string(),
+        ));
+    }
     // vote: block item_ref comparison item_ref
     let s = tail.trim_start();
     if s.is_empty() {
@@ -462,6 +584,11 @@ fn parse_item_definition_statement(stripped: &str, masker: &BlockMasker) -> Resu
     }
 
     if let Some((tok, end)) = parse_block_token_at(stripped, i) {
+        if masker.block_kind(&tok) == Some(BlockKind::CodeFence) {
+            return Err(DslError::Parse(
+                "item bodies must use `{ ... }`; code fences belong inside body blocks".to_string(),
+            ));
+        }
         let body = masker.extract_body(&tok);
         let tail = stripped[end..].trim();
         if !tail.is_empty() {
@@ -572,6 +699,10 @@ pub fn parse_full(text: &str) -> Result<Document, DslError> {
 
             if let Some((tok, end)) = parse_block_token_at(stripped, 0) {
                 if stripped[end..].trim().is_empty() {
+                    if masker.block_kind(&tok) == Some(BlockKind::CodeFence) {
+                        prose_buffer.push(line);
+                        continue;
+                    }
                     pending_block = Some(tok);
                     continue;
                 }
@@ -619,6 +750,70 @@ mod tests {
         assert_eq!(roundtrip, input);
     }
 
+    #[test]
+    fn blockmasker_tokens_are_deterministic_and_typed() {
+        let input = "x ```code``` y {body}";
+        let (masker, masked) = mask_all(BlockMasker::new(), input);
+        assert!(masked.contains("__BLOCK_00000000__"));
+        assert!(masked.contains("__BLOCK_00000001__"));
+        assert_eq!(
+            masker.block_kind("__BLOCK_00000000__"),
+            Some(BlockKind::CodeFence)
+        );
+        assert_eq!(
+            masker.block_kind("__BLOCK_00000001__"),
+            Some(BlockKind::Brace)
+        );
+        assert_eq!(masker.unmask(&masked), input);
+    }
+
+    #[test]
+    fn prose_tokenizer_finds_tilde_dash_and_raw_url_refs() {
+        let tokens =
+            tokenize_prose_item_refs("see ~/a/b then -/example.com/x and https://Example.com/A/B.");
+        assert_eq!(
+            tokens,
+            vec![
+                ProseToken::Text("see ".to_string()),
+                ProseToken::ItemRef("~/a/b".to_string()),
+                ProseToken::Text(" then ".to_string()),
+                ProseToken::ItemRef("-/example.com/x".to_string()),
+                ProseToken::Text(" and ".to_string()),
+                ProseToken::ItemRef("https://Example.com/A/B".to_string()),
+                ProseToken::Text(".".to_string()),
+            ]
+        );
+    }
+
+    #[test]
+    fn prose_tokenizer_stops_raw_urls_at_newlines() {
+        let tokens = tokenize_prose_item_refs("https://example.com/a/b.\n-/example.com/a/b");
+        assert_eq!(
+            tokens,
+            vec![
+                ProseToken::ItemRef("https://example.com/a/b".to_string()),
+                ProseToken::Text(".\n".to_string()),
+                ProseToken::ItemRef("-/example.com/a/b".to_string()),
+            ]
+        );
+    }
+
+    #[test]
+    fn prose_tokenizer_does_not_linkify_inside_code_fences() {
+        let tokens = tokenize_prose_item_refs(
+            "before ```json\n{\"url\":\"https://example.com\"}\n``` after ~/x",
+        );
+        assert_eq!(
+            tokens,
+            vec![
+                ProseToken::Text(
+                    "before ```json\n{\"url\":\"https://example.com\"}\n``` after ".to_string()
+                ),
+                ProseToken::ItemRef("~/x".to_string()),
+            ]
+        );
+    }
+
     #[test]
     fn parse_item_with_body_strips_outer_braces() {
         let input = "~/rust { Systems language }";
@@ -633,8 +828,8 @@ mod tests {
     }
 
     #[test]
-    fn parse_item_with_fenced_json_body_preserves_braces() {
-        let input = "~/item/in/url ```json\n{\"test\": true}\n```";
+    fn parse_item_with_braced_fenced_json_body_preserves_braces() {
+        let input = "~/item/in/url {\n```json\n{\"test\": true}\n```\n}";
         let doc = parse_full(input).unwrap();
         assert_eq!(
             doc.statements,
@@ -645,6 +840,51 @@ mod tests {
     

… preview truncated; 18,752 characters omitted

download full diff A

B — c_7a129e904906 (tommy-mor)

message

[4e327784] deploy live constitution dashboard

Expose auditable progress and event streaming, configure the production roots and runtime, and make tested main-branch commits the deployment authority.

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

diff preview

diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000000000000000000000000000000000000..c9d63a722beba0a0297fc853089332c460ab78dd
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,7 @@
+.git
+.venv
+.hypothesis
+__pycache__
+tests
+*.json
+*.bsp
diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
new file mode 100644
index 0000000000000000000000000000000000000000..76dcdf82d53177c1e47d86b23a54523239d232a6
--- /dev/null
+++ b/.github/workflows/deploy.yml
@@ -0,0 +1,49 @@
+name: Test and deploy
+
+on:
+  push:
+    branches: [main]
+
+concurrency:
+  group: production
+  cancel-in-progress: false
+
+permissions:
+  contents: read
+
+jobs:
+  test:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v4
+
+      - uses: astral-sh/setup-uv@v6
+        with:
+          enable-cache: true
+
+      - name: Run Python tests
+        run: uv run pytest -q
+
+      - name: Install Babashka
+        run: |
+          curl -fsSL https://raw.githubusercontent.com/babashka/babashka/master/install \
+            | sudo bash -s -- --dir /usr/local/bin
+
+      - name: Run process integration tests
+        run: bb TEST.sh
+
+  deploy:
+    needs: test
+    runs-on: ubuntu-latest
+    environment:
+      name: production
+      url: https://token.slug.social
+    steps:
+      - uses: actions/checkout@v4
+
+      - uses: superfly/flyctl-actions/setup-flyctl@master
+
+      - name: Deploy to Fly
+        run: flyctl deploy --remote-only
+        env:
+          FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..c9a5c00782371c19ad5ab5c58cf6f5a8ffec0141
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,17 @@
+FROM ghcr.io/astral-sh/uv:python3.11-bookworm-slim
+
+RUN apt-get update \
+    && apt-get install -y --no-install-recommends git ca-certificates \
+    && rm -rf /var/lib/apt/lists/*
+
+WORKDIR /app
+COPY pyproject.toml uv.lock ./
+RUN uv sync --frozen --no-install-project
+
+COPY constitution.py ./
+
+ENV PATH="/app/.venv/bin:${PATH}" \
+    PYTHONUNBUFFERED="1"
+
+EXPOSE 8080
+CMD ["python", "constitution.py"]
diff --git a/constitution.py b/constitution.py
index 4bee9f83663ab7fb36db95129b92b64b4ef57258..a58257e1881b21d1d6fa8e68a3faa222e4f661ef 100644
--- a/constitution.py
+++ b/constitution.py
@@ -24,12 +24,12 @@ A daily GitHub Action backs up the JSONL ledger to the same repo.
 Run: uv run constitution.py
 """
 
-from decimal import Decimal, getcontext
+from decimal import Decimal, getcontext, DefaultContext
 from datetime import datetime, timezone
 from fastapi import FastAPI, Request, Response
 from fastapi.responses import PlainTextResponse, HTMLResponse
 from starlette.middleware.sessions import SessionMiddleware
-import json, time, os, asyncio, httpx, pathlib, subprocess, hashlib, re, fcntl
+import json, time, os, asyncio, httpx, pathlib, subprocess, hashlib, re, fcntl, base64
 import sympy as sp  # type: ignore[reportMissingImports]
 from tenacity import retry, retry_if_exception, stop_after_attempt, wait_exponential
 from evaleval import (
@@ -37,6 +37,7 @@ from evaleval import (
     exec_event, One, Two, Three, Selector, MORPH, PREPEND,
 )
 
+DefaultContext.prec = 50
 getcontext().prec = 50
 
 app = FastAPI()
@@ -129,14 +130,47 @@ OPENROUTER_BASE_URL = os.environ.get("OPENROUTER_BASE_URL", "https://openrouter.
 # using the exact same source; their normalized values are committed to every
 # discovery event.
 DEFAULT_REPOSITORIES = [
+    {
+        "id": "constitution",
+        "url": "https://github.com/sortersocial/constitution.git",
+        "refs": ["refs/heads/**"],
+    },
     {
         "id": "slug",
-        "url": "https://github.com/tommy-mor/slug.git",
+        "url": "https://github.com/sortersocial/slug.git",
+        "refs": ["refs/heads/**"],
+    },
+    {
+        "id": "sorter",
+        "url": "https://github.com/sorterisntonline/sorter.git",
+        "refs": ["refs/heads/**"],
+    },
+    {
+        "id": "sorter2",
+        "url": "https://github.com/sortersocial/sorter2.git",
+        "refs": ["refs/heads/**"],
+    },
+    {
+        "id": "sorter-oldest",
+        "url": "https://github.com/tommy-mor/sorter.git",
         "refs": ["refs/heads/**"],
     },
 ]
 DEFAULT_CONTRIBUTORS = {
     "tommy-mor": ["thmorriss@gmail.com"],
+    "christopher-whitman": [
+        "chris@cwwhitman.com",
+        "7566903+cwwhitman@users.noreply.github.com",
+    ],
+    "jake-chvatal": [
+        "jake+github@uln.industries",
+        "jakechvatal@gmail.com",
+        "jake@isnt.online",
+    ],
+    "lara": ["me@lara.lv"],
+    "nat-reid": ["nathanielreid@gmail.com"],
+    "zod": ["jason.p.mcel@gmail.com", "me@zod.tf"],
+    "jovan": ["jovan@slug.social", "jovan@getcivicai.com"],
 }
 
 REPOSITORIES = json.loads(
@@ -147,6 +181,7 @@ CONTRIBUTORS = json.loads(
 )
 GIT_MIRROR_DIR = pathlib.Path(os.environ.get("GIT_MIRROR_DIR", "/data/git"))
 GIT_TIMEOUT_SECONDS = int(os.environ.get("GIT_TIMEOUT_SECONDS", "120"))
+GITHUB_TOKEN = os.environ.get("GITHUB_TOKEN", "")
 
 # Council model IDs: slug.social garden rank under this parent (bodies = OpenRouter URLs), then top-up from OpenRouter list.
 SLUG_SOCIAL_BASE_URL = os.environ.get("SLUG_SOCIAL_BASE_URL", "https://slug.social").rstrip("/")
@@ -661,20 +696,30 @@ def _git(repo: pathlib.Path | None, *args: str, input_bytes: bytes | None = None
     if repo is not None:
         command += ["-C", str(repo)]
     command += list(args)
+    git_env = {
+        **os.environ,
+        "GIT_CONFIG_NOSYSTEM": "1",
+        "GIT_CONFIG_GLOBAL": os.devnull,
+        "GIT_NO_REPLACE_OBJECTS": "1",
+        "LC_ALL": "C",
+        "TZ": "UTC",
+    }
+    if GITHUB_TOKEN:
+        credential = base64.b64encode(
+            f"x-access-token:{GITHUB_TOKEN}".encode()
+        ).decode()
+        git_env.update({
+            "GIT_CONFIG_COUNT": "1",
+            "GIT_CONFIG_KEY_0": "http.https://github.com/.extraHeader",
+            "GIT_CONFIG_VALUE_0": f"Authorization: Basic {credential}",
+        })
     try:
         result = subprocess.run(
             command,
             input=input_bytes,
             stdout=subprocess.PIPE,
             stderr=subprocess.PIPE,
-            env={
-                **os.environ,
-                "GIT_CONFIG_NOSYSTEM": "1",
-                "GIT_CONFIG_GLOBAL": os.devnull,
-                "GIT_NO_REPLACE_OBJECTS": "1",
-                "LC_ALL": "C",
-                "TZ": "UTC",
-            },
+            env=git_env,
             timeout=GIT_TIMEOUT_SECONDS,
             check=False,
         )
@@ -844,9 +889,15 @@ def _build_discovery(epoch_n: int, boundary_ms: int, events: list) -> GitDiscove
         canonical_location = min(
             locations[qualified_oid], key=lambda x: (x[0], x[1])
         )
+        # One commit may be reachable from dozens of refs in the same mirror.
+        # Verify its object once per repository, not once per source ref.
+        object_locations = {
+            (str(m), raw_oid): (m, raw_oid)
+            for _, _, m, raw_oid in locations[qualified_oid]
+        }
         object_hashes = {
             hashlib.sha256(_git(m, "cat-file", "commit", raw_oid)).hexdigest()
-            for _, _, m, raw_oid in locations[qualified_oid]
+            for m, raw_oid in object_locations.values()
         }
         if len(object_hashes) != 1:
             raise RuntimeError(f"conflicting Git objects share OID {qualified_oid}")
@@ -994,11 +1045,55 @@ async def discover_repositories(epoch_n: int, boundary_ms: int) -> GitDiscovery:
 
 
 SSE_CLIENTS = []
+AUDIT_HISTORY = []
+AUDIT_SEQUENCE = 0
+PROCESS_STATE = {
+    "running": False,
+    "phase": "idle",
+    "progress": 100,
+    "message": "Waiting for the next epoch",
+}
+
+
+def _sse_event(event_name: str, payload: dict) -> str:
+    return (
+        f"event: {event_name}\n"
+        f"data: {json.dumps(payload, separators=(',', ':'))}\n\n"
+    )
+
+
+async def broadcast_audit(
+    kind: str,
+    message: str,
+    *,
+    progress: int | None = None,
+    phase: str | None = None,
+) -> dict:
+    global AUDIT_SEQUENCE
+    AUDIT_SEQUENCE += 1
+    if progress is not None:
+        PROCESS_STATE["progress"] = max(0, min(100, int(progress)))
+    if phase is not None:
+        PROCESS_STATE["phase"] = phase
+    PROCESS_STATE["message"] = message
+    payload = {
+        "id": AUDIT_SEQUENCE,
+        "timestamp_ms": int(time.time() * 1000),
+        "kind": kind,
+        "message": message,
+        **PROCESS_STATE,
+    }
+    AUDIT_HISTORY.append(payload)
+    del AUDIT_HISTORY[:-200]
+    wire = _sse_event("audit", payload)
+    for queue in list(SSE_CLIENTS):
+        await queue.put(wire)
+    return payload
 
 
 async def broadcast_js(js: str):
     """Send a JS snippet to all connected SSE clients."""
-    for queue in SSE_CLIENTS:
+    for queue in list(SSE_CLIENTS):
         await queue.put(js)
 
 
@@ -1006,10 +1101,29 @@ async def rank_commits(commits: list[dict]):
     if not commits:
         return {}, []
 
-    models = await fetch_top_models(n=3)
     contributors = sorted(set(c["contributor"] for c in commits))
-    if len(contributors) > 1 and not models:
+    if len(contributors) == 1:
+        await broadcast_audit(
+            "ranking",
+            f"Only {contributors[0]} is eligible; rank is 1.0",
+            progress=90,
+            phase="finalizing",
+        )
+        return {contributors[0]: Decimal("1")}, []
+    if not (OPENROUTER_API_KEY or "").strip():
+        raise RuntimeError(
+            "OPENROUTER_API_KEY is required when multiple contributors need ranking"
+        )
+
+    models = await fetch_top_models(n=3)
+    if not models:
         raise RuntimeError("no council models available for contributor ranking")
+    await broadcast_audit(
+        "council",
+        f"Council selected: {', '.join(models)}",
+        progress=35,
+        phase="ranking",
+    )
     await broadcast_js(exec_event(Three[Selector("#emission-log")][PREPEND][
         ["div.log-council", f"Council: {', '.join(models)} — {len(commits)} commits"]
     ]))
@@ -1035,6 +1149,11 @@ async def rank_commits(commits: list[dict]):
 
     async def compare_fn(i, j):
         a1, a2 = authors[i], authors[j]
+        await broadcast_audit(
+            "comparison",
+            f"Comparing {a1} with {a2}",
+            phase="ranking",
+        )
         await broadcast_js(exec_event(Three[Selector("#emission-status")][MORPH][
             ["div#emission-status", f"Comparing {a1} vs {a2}…"]
         ]))
@@ -1050,6 +1169,11 @@ async def rank_commits(commits: list[dict]):
                 if winner_weight <= 0 or loser_weight <= 0:
                     raise ValueError("ratio weights must be positive")
                 results.append((w, l, winner_weight, loser_weight))
+                await broadcast_audit(
+                    "vote",
+                    f"{model}: {authors[w]} over {authors[l]} ({result['ratio']})",
+                    phase="ranking",
+                )
                 await broadcast_js(exec_event(Three[Selector("#emission-log")][PREPEND][
                     ["div.log-vote",
                         ["span.model", model], " — ",
@@ -1059,6 +1183,11 @@ async def rank_commits(commits: list[dict]):
                     ]
                 ]))
             except Exception as e:
+                await broadcast_audit(
+                    "error",
+                    f"{model} failed: {e}",
+                    phase="error",
+                )
                 await broadcast_js(exec_event(Three[Selector("#emission-log")][PREPEND][
                     ["div.log-error", f"⚠ {model}: {e}"]
                 ]))
@@ -1068,8 +1197,13 @@ async def rank_commits(commits: list[dict]):
     async def progress_fn(ev):
         if ev["phase"] == "spanning_tree":
             label = f"Spanning tree: {ev['step']}/{ev['total']}"
+    

… preview truncated; 28,089 characters omitted

download full diff B

Hardlinks — judgments / attempts / prompt

prompt download

judgments

attempts

Prompt text is loaded only by the download route.