Side A performs a genuine architectural consolidation: it removes a 362-line duplicated web_post.rs module, unifies three separate POST endpoints into a single RPC-dispatched /ui handler with a shared session-resolution helper, and updates all call sites (routes, templates, integration tests, fixture) consistently. Side B mostly deletes legacy fallback branches in one file (constitution.py) for cleanliness, which is useful but narrower in scope and forces a production ledger wipe due to now-required Emission fields, making it a riskier, less self-contained change than Side A's thorough end-to-end dedup.
constitution · epochs · watch · epoch 3
c_effff18688f9 (tommy-mor) vs c_6f04dcb2e38c (tommy-mor)
download prompt · raw event · cmp_06c5e4a563c4b4
council reasoning
A completes a real architectural migration: deletes the separate `/post` stack and `web_post.rs`, consolidates auth into `resolve_web_session`/`WebSession`, and routes browser ingest/check/redact through a single `POST /ui` + `__rpc__` path with matching HTML/JS and tests. B is a worthwhile correctness cleanup (drop GitDiscovery legacy projection, require Emission evidence fields), but it is mostly deletion of display fallbacks rather than a multi-surface design change of comparable scope.
Side B removes the legacy GitDiscovery projection path and makes the evidence model authoritative by requiring emission metadata, deleting fallback lookup code, and simplifying epoch/commit pages to rely only on Evidence events. This is a substantive architectural cleanup that eliminates dual data paths and updates tests accordingly, whereas Side A is largely a large-scale refactor consolidating web posting into the `/ui` RPC flow with mostly equivalent behavior moved between modules.
sides
A — c_effff18688f9 (tommy-mor)
message
[c3cbcaa7] refactor
diff preview
diff --git a/server/src/api/auth.rs b/server/src/api/auth.rs
index b3631b06153d52f88348fef927e7a024b7b85ad6..cd556eb89e9dd8203eba6c8969ffd144db5d329d 100644
--- a/server/src/api/auth.rs
+++ b/server/src/api/auth.rs
@@ -71,6 +71,24 @@ pub fn optional_principal(headers: &HeaderMap, jar: &CookieJar, reduced: &Reduce
verify_token(reduced, c.value()).ok()
}
+/// Browser session: principal + bearer token string (same shape as CLI session cookie).
+#[derive(Debug, Clone)]
+pub struct WebSession {
+ pub username: String,
+ pub bearer: String,
+}
+
+/// Resolve username and bearer together for `POST /ui` dispatch (one read of headers + jar).
+pub fn resolve_web_session(headers: &HeaderMap, jar: &CookieJar, reduced: &ReducerState) -> Option<WebSession> {
+ let username = optional_principal(headers, jar, reduced)?;
+ let bearer = headers
+ .get(header::AUTHORIZATION)
+ .and_then(|v| v.to_str().ok())
+ .and_then(|s| s.strip_prefix("Bearer ").map(|t| t.trim().to_string()))
+ .or_else(|| jar.get(SLUG_SESSION_COOKIE).map(|c| c.value().to_string()))?;
+ Some(WebSession { username, bearer })
+}
+
fn redirect_with_session_cookie(public_url: &str, path_and_query: &str, bearer: &str, jar: &CookieJar) -> Response {
let mut res = Response::builder()
.status(StatusCode::TEMPORARY_REDIRECT)
diff --git a/server/src/api/mod.rs b/server/src/api/mod.rs
index a986f706ea4b261cbaf004c02b4cf84184b41371..4e223a7460997c464706dca850281447bd754ed5 100644
--- a/server/src/api/mod.rs
+++ b/server/src/api/mod.rs
@@ -4,7 +4,6 @@ mod rpc;
mod stream;
mod validate;
mod ui_html;
-mod web_post;
pub use auth::{
get_join_invite,
@@ -19,7 +18,9 @@ pub use auth::{
get_web_login,
get_logout,
optional_principal,
+ resolve_web_session,
session_cookie_header_value,
+ WebSession,
SLUG_SESSION_COOKIE,
};
@@ -35,7 +36,6 @@ pub use stream::{get_html_stream, get_stream};
pub use validate::{normalize_room_and_thread, validate_ingest_document, ValidatedIngest};
pub use ui_html::post_ui_html;
-pub use web_post::{check_web_ingest, post_web_ingest, post_web_redact};
#[cfg(test)]
mod tests {
diff --git a/server/src/api/ui_html.rs b/server/src/api/ui_html.rs
index 2b40a72059981d558768f73d189b991f3448c257..497f8fdd222a8ec7c3d76b0695e0352e973ca3ba 100644
--- a/server/src/api/ui_html.rs
+++ b/server/src/api/ui_html.rs
@@ -1,25 +1,29 @@
-//! Single `POST /ui` entry for browser [`crate::html::ui_action::HtmlUiAction`] (JSON in `__rpc__` + holes).
+//! Single `POST /ui` entry: parse `__rpc__` → [`HtmlUiAction`], resolve [`WebSession`] once, dispatch.
use axum::{
- body::Body,
+ body,
extract::State,
- http::{header, HeaderMap, StatusCode},
+ http::{header, HeaderMap, HeaderValue, StatusCode},
response::{IntoResponse, Response},
Form,
};
use axum_extra::extract::cookie::CookieJar;
+use slug_types::{RpcBatch, RpcBatchResponse, RpcCommand, RpcResult};
use std::collections::HashMap;
use crate::{
api::{
- auth::optional_principal,
- web_post::{run_check_web_ingest, run_post_web_ingest, run_post_web_redact, WebPostForm, WebRedactForm},
+ auth::{resolve_web_session, WebSession},
+ handle_rpc_batch,
+ rpc::{rpc_post_redact, rpc_post_with_bearer},
},
+ canonical_path::canonicalize_tag,
html::{
fragment_public_new_thread_form, fragment_room_new_thread_form, login_to_post_hint_markup,
- parse_html_ui_from_form, user_can_post_room, user_can_view_room, HtmlUiAction, JsBuilder,
- ThreadNav,
+ parse_html_ui_from_form, thread_feed_html, thread_feed_html_for_room, thread_feed_region_markup,
+ user_can_post_room, user_can_view_room, HtmlUiAction, JsBuilder, ThreadNav,
},
+ reducer::{scope_from_room_wire, ScopeId},
state::AppState,
};
@@ -34,6 +38,19 @@ pub async fn post_ui_html(
Err(e) => return ui_js_warn(&e.to_string()).into_response(),
};
+ let reduced = state.reduced.read().await;
+ let session = resolve_web_session(&headers, &jar, &reduced);
+ drop(reduced);
+
+ dispatch_ui_action(&state, session.as_ref(), action).await
+}
+
+/// All UI command logic: HTTP extractors stop above; this only sees [`AppState`], session, and [`HtmlUiAction`].
+async fn dispatch_ui_action(
+ state: &AppState,
+ session: Option<&WebSession>,
+ action: HtmlUiAction,
+) -> Response {
match action {
HtmlUiAction::PostIngest {
room,
@@ -42,47 +59,87 @@ pub async fn post_ui_html(
error_target,
form_id,
} => {
- run_post_web_ingest(
- &state,
- &headers,
- &jar,
- WebPostForm {
- room,
- thread_tag,
- text,
- error_target,
- form_id,
- },
- )
- .await
+ let Some(session) = session else {
+ return js_redirect("/login").into_response();
+ };
+ let room = room.trim().to_string();
+ let thread_tag = thread_tag.trim().to_string();
+ if text.trim().is_empty() {
+ return form_js_error(
+ error_target.as_ref(),
+ "empty post",
+ "Write something in the text area (DSL / prose).",
+ )
+ .into_response();
+ }
+ match rpc_post_with_bearer(state, &session.bearer, room.clone(), thread_tag.clone(), text).await {
+ Ok(RpcResult::PostOk { .. }) => {
+ post_success_response(
+ state,
+ &room,
+ &thread_tag,
+ error_target.as_ref(),
+ form_id.as_ref(),
+ Some(session.username.as_str()),
+ )
+ .await
+ .into_response()
+ }
+ Ok(_) => form_js_error(
+ error_target.as_ref(),
+ "unexpected response",
+ "Post did not return PostOk.",
+ )
+ .into_response(),
+ Err((msg, hint)) => form_js_error(error_target.as_ref(), &msg, hint.as_deref().unwrap_or("")).into_response(),
+ }
}
HtmlUiAction::CheckIngest {
room,
thread_tag,
text,
error_target,
- form_id,
+ form_id: _,
} => {
- run_check_web_ingest(
- &state,
- &headers,
- &jar,
- WebPostForm {
- room,
- thread_tag,
- text,
- error_target,
- form_id,
- },
- )
- .await
+ let Some(session) = session else {
+ return js_redirect("/login").into_response();
+ };
+ let room = room.trim().to_string();
+ let thread_tag = canonicalize_tag(&thread_tag);
+ if thread_tag.is_empty() {
+ return form_js_error(
+ error_target.as_ref(),
+ "missing thread tag",
+ "Set a thread tag before posting.",
+ )
+ .into_response();
+ }
+ if text.trim().is_empty() {
+ return js_clear_errors(&form_error_target(error_target.as_ref())).into_response();
+ }
+ match rpc_check_with_bearer(state, &session.bearer, room, text.clone()).await {
+ Ok(RpcResult::CheckOk { .. }) => js_clear_errors(&form_error_target(error_target.as_ref())).into_response(),
+ Ok(_) => form_js_error(error_target.as_ref(), "unexpected response", "Check did not return CheckOk.").into_response(),
+ Err((msg, hint)) => form_js_error(error_target.as_ref(), &msg, hint.as_deref().unwrap_or("")).into_response(),
+ }
}
HtmlUiAction::RedactPost { post_id } => {
- run_post_web_redact(&state, &headers, &jar, WebRedactForm { post_id }).await
+ let Some(session) = session else {
+ return js_redirect("/login").into_response();
+ };
+ let h = headers_from_bearer(&session.bearer);
+ match rpc_post_redact(state, &h, post_id).await {
+ Ok(RpcResult::RedactPostOk {}) => redact_success_response(state).await.into_response(),
+ Ok(_) => (StatusCode::BAD_REQUEST, "unexpected response").into_response(),
+ Err((msg, hint)) => {
+ let detail = hint.as_deref().unwrap_or("");
+ js_error("#errors", &msg, detail).into_response()
+ }
+ }
}
HtmlUiAction::ExpandPublicNewThreadForm => {
let reduced = state.reduced.read().await;
- let user = optional_principal(&headers, &jar, &reduced);
+ let user = session.map(|s| s.username.as_str());
drop(reduced);
let markup = if user.is_some() {
fragment_public_new_thread_form(true)
@@ -99,18 +156,18 @@ pub async fn post_ui_html(
return ui_js_warn("missing room").into_response();
}
let reduced = state.reduced.read().await;
- let user = optional_principal(&headers, &jar, &reduced);
+ let user = session.map(|s| s.username.as_str());
if !reduced.rooms.contains(&room_wire) {
drop(reduced);
return ui_js_warn("room not found").into_response();
}
- if !user_can_view_room(&reduced, &room_wire, user.as_deref()) {
+ if !user_can_view_room(&reduced, &room_wire, user) {
drop(reduced);
return ui_js_warn("forbidden").into_response();
}
- let can_post = user
+ let can_post = session
.as_ref()
- .map(|u| user_can_post_room(&reduced, &room_wire, u))
+ .map(|s| user_can_post_room(&reduced, &room_wire, &s.username))
.unwrap_or(false);
drop(reduced);
let Some(nav) = ThreadNav::from_room_id(&room_wire) else {
@@ -128,12 +185,195 @@ pub async fn post_ui_html(
}
}
+fn headers_from_bearer(bearer: &str) -> HeaderMap {
+ let mut headers = HeaderMap::new();
+ if let Ok(hv) = HeaderValue::from_str(&format!("Bearer {bearer}")) {
+ headers.insert(header::AUTHORIZATION, hv);
+ }
+ headers
+}
+
+fn post_redirect_location(room: &str, thread_tag: &str) -> String {
+ let tag = canonicalize_tag(thread_tag);
+ if room.trim() == "public" {
+ format!("/t/{tag}")
+ } else {
+ let room = room.trim();
+ let Some((a, b)) = room.split_once('/') else {
+ return "/".to_string();
+ };
+ format!("/r/{a}/{b}/t/{tag}")
+ }
+}
+
+fn js_quote(s: &str) -> String {
+ serde_json::to_string(s).expect("js string escaping must succeed")
+}
+
+fn js_redirect(to: &str) -> Response {
+ let js = format!("window.location = {};", js_quote(to));
+ Response::builder()
+ .status(StatusCode::OK)
+ .header(header::CONTENT_TYPE, "text/javascript; charset=utf-8")
+ .body(axum::body::Body::from(js))
+ .unwrap()
+}
+
+fn js_error(error_target: &str, title: &str, detail: &str) -> Response {
+ let markup = maud::html! {
+ div id=(error_target.trim_start_matches('#')) {
+ p class="auth-error" { (title) }
+ @if !detail.is_empty() {
+ pre class="muted" { (detail) }
+ }
+ }
+ };
+ JsBuilder::new()
+ .morph_selector(error_targe
… preview truncated; 33,445 characters omittedB — c_6f04dcb2e38c (tommy-mor)
message
[bda5f8aa] Remove legacy evidence projection; Evidence envelopes only. Epoch and commit pages no longer invent history from bare GitDiscovery rows. Production ledger will be wiped to re-emit under the current schema. Co-authored-by: Cursor <cursoragent@cursor.com>
diff preview
diff --git a/constitution.py b/constitution.py
index 4dd5b9dfbba231d46289c490f91b1dd5b1018bcf..26ba130e885e0e69fb7874ca5c3f07f42100a150 100644
--- a/constitution.py
+++ b/constitution.py
@@ -210,10 +210,10 @@ class Emission:
distributions: dict # author -> amount str
ranking: dict # author -> score str
models_used: list
- discovery_snapshot_id: str = "" # empty only for pre-discovery ledger history
- evidence_schema_version: int = 1
- ranking_run_id: str = ""
- ranking_event_id: str = ""
+ discovery_snapshot_id: str
+ evidence_schema_version: int
+ ranking_run_id: str
+ ranking_event_id: str
@event
@@ -502,26 +502,6 @@ def _epochs_in_ledger() -> list[int]:
return sorted(epochs)
-def _legacy_commit_row(commit_id: str) -> tuple[GitDiscovery | None, dict | None]:
- for discovery in store.read():
- if not isinstance(discovery, GitDiscovery):
- continue
- for commit in discovery.commits:
- if commit_id_for_oid(commit["oid"]) == commit_id:
- return discovery, commit
- return None, None
-
-
-def _legacy_observation(commit_id: str) -> tuple[GitDiscovery | None, dict | None]:
- for discovery in store.read():
- if not isinstance(discovery, GitDiscovery):
- continue
- for obs in discovery.observations:
- if commit_id_for_oid(obs["oid"]) == commit_id:
- return discovery, obs
- return None, None
-
-
def build_pairwise_prompt(side_a: dict, side_b: dict) -> str:
return f"""You are ranking contributions to an open source project.
Compare these two sides (each may be one or more commits). Decide which side contributed more.
@@ -2040,7 +2020,7 @@ def _strip_heavy_fields(obj: dict) -> dict:
@app.get("/api/ledger")
async def get_ledger(offset: int = 0, limit: int = 100, full: int = 0):
- """List of ledger dicts (backward-compatible). Heavy blobs stripped unless full=1."""
+ """List of ledger dicts. Heavy blobs stripped unless full=1."""
limit = max(1, min(limit, 500))
rows = []
for e in store.read()[offset:offset + limit]:
@@ -2274,23 +2254,25 @@ async def epochs_index():
epochs = _epochs_in_ledger()
rows = []
for epoch in epochs:
- discovery = _discovery_for_epoch(epoch)
emission = _emission_for_epoch(epoch)
- evidence_n = sum(
- 1 for e in evidence_by_kind() if e.epoch == epoch
+ evidence_n = sum(1 for e in evidence_by_kind() if e.epoch == epoch)
+ disc = next(
+ (
+ e for e in evidence_by_kind("git.discovery_completed")
+ if e.epoch == epoch
+ ),
+ None,
)
detail = []
- if discovery:
+ if disc:
detail.append(
- f"{len(discovery.commits)} eligible / "
- f"{len(discovery.observations)} observed"
+ f"{disc.payload.get('eligible_count', 0)} eligible / "
+ f"{disc.payload.get('observation_count', 0)} observed"
)
if emission:
detail.append(f"emitted {emission.total_emitted}")
if evidence_n:
detail.append(f"{evidence_n} evidence events")
- elif discovery or emission:
- detail.append("legacy (no Evidence envelopes)")
rows.append(["li",
_a(_evidence_path("epoch", str(epoch)), f"epoch {epoch}"),
" — ",
@@ -2307,37 +2289,37 @@ async def epochs_index():
@app.get("/epochs/{epoch}")
async def epoch_detail(epoch: int):
- discovery = _discovery_for_epoch(epoch)
emission = _emission_for_epoch(epoch)
evidence_rows = [e for e in evidence_by_kind() if e.epoch == epoch]
+ if not evidence_rows and emission is None:
+ return _evidence_page(f"epoch {epoch}", [
+ _evidence_nav(),
+ ["h1", f"epoch {epoch}"],
+ ["p.note", "No evidence for this epoch."],
+ ])
+
commit_evs = [e for e in evidence_rows if e.kind == "git.commit"]
comparison_evs = [e for e in evidence_rows if e.kind == "comparison.input"]
judgment_evs = [e for e in evidence_rows if e.kind == "llm.judgment"]
+ discovery_ev = next(
+ (e for e in evidence_rows if e.kind == "git.discovery_completed"), None
+ )
ranking_started = next(
(e for e in evidence_rows if e.kind == "ranking.started"), None
)
ranking_completed = next(
(e for e in evidence_rows if e.kind == "ranking.completed"), None
)
- legacy = not evidence_rows and (discovery is not None or emission is not None)
-
- commit_links: list[tuple[str, str]] = []
- if commit_evs:
- for e in commit_evs:
- cid = e.payload.get("commit_id") or ""
- label = (
- f"{e.payload.get('oid', cid)[:24]} "
- f"({e.payload.get('contributor', '?')})"
- )
- commit_links.append((label, _evidence_path("commit", cid)))
- elif discovery:
- for c in discovery.commits:
- cid = commit_id_for_oid(c["oid"])
- commit_links.append((
- f"{c['oid'][:24]} ({c.get('contributor', '?')})",
- _evidence_path("commit", cid),
- ))
+ commit_links = [
+ (
+ f"{e.payload.get('oid', e.payload.get('commit_id', ''))[:24]} "
+ f"({e.payload.get('contributor', '?')})",
+ _evidence_path("commit", e.payload["commit_id"]),
+ )
+ for e in commit_evs
+ if e.payload.get("commit_id")
+ ]
comparison_links = [
(
e.payload.get("summary") or e.payload.get("comparison_id", e.event_id),
@@ -2360,16 +2342,13 @@ async def epoch_detail(epoch: int):
]
excluded = []
- if discovery:
- for obs in discovery.observations:
+ if discovery_ev:
+ for obs in discovery_ev.payload.get("observations") or []:
if obs.get("eligible"):
continue
oid = obs.get("oid", "?")
reason = obs.get("exclusion_reason") or "excluded"
- excluded.append(["li",
- f"{oid[:28]} — {reason} — ",
- ["span.note", "legacy evidence unavailable"],
- ])
+ excluded.append(["li", f"{oid[:28]} — {reason}"])
ranking_nodes: list = []
if ranking_completed:
@@ -2388,21 +2367,10 @@ async def epoch_detail(epoch: int):
indent=2, sort_keys=True,
)],
]
- elif emission:
- if legacy and len(emission.ranking or {}) <= 1:
- ranking_nodes.append(["p.note",
- "Single-contributor epoch — no LLM judgments."
- ])
- ranking_nodes.extend([
- ["p", "Projected from Emission (no ranking Evidence event)."],
- ["pre.blob", json.dumps(emission.ranking, indent=2, sort_keys=True)],
- ])
+ elif ranking_started:
+ ranking_nodes = [["p.note", f"Ranking started: {ranking_started.event_id}"]]
else:
- ranking_nodes = [["p.note", "No ranking recorded."]]
- if ranking_started and not ranking_completed:
- ranking_nodes.insert(0, ["p.note",
- f"Ranking started: {ranking_started.event_id}"
- ])
+ ranking_nodes = [["p.note", "No ranking evidence."]]
if emission:
emission_node = _dl_rows([
@@ -2411,44 +2379,41 @@ async def epoch_detail(epoch: int):
("pool_after", emission.pool_after),
("discovery_snapshot_id", emission.discovery_snapshot_id),
("ranking_run_id", emission.ranking_run_id or None),
+ ("ranking_event_id", emission.ranking_event_id or None),
("models_used", ", ".join(emission.models_used or [])),
("distributions", json.dumps(emission.distributions, sort_keys=True)),
])
else:
emission_node = ["p.note", "No emission for this epoch."]
- single_contributor = False
- if discovery:
- single_contributor = len({c.get("contributor") for c in discovery.commits}) <= 1
- elif emission:
- single_contributor = len(emission.ranking or {}) <= 1
+ contributors = {
+ e.payload.get("contributor")
+ for e in commit_evs
+ if e.payload.get("contributor")
+ }
+ no_comparisons_note = "No comparisons."
+ if len(contributors) <= 1:
+ no_comparisons_note += " Single-contributor — no LLM judgments."
body = [
_evidence_nav(),
["div.eyebrow", f"epoch {epoch}"],
["h1", f"epoch {epoch}"],
]
- if legacy:
- body.append(["p.note",
- "Legacy epoch: projected from GitDiscovery/Emission without Evidence "
- "envelopes. Eligible commits use discovery patches; discarded observation "
- "metadata is marked legacy evidence unavailable. Single-contributor "
- "epochs have no LLM judgments."
- ])
- if discovery:
+ if discovery_ev:
body.extend([
["h2", "discovery"],
_dl_rows([
- ("snapshot_id", discovery.snapshot_id),
- ("config_digest", discovery.config_digest),
- ("initial_snapshot", discovery.initial_snapshot),
- ("observations", len(discovery.observations)),
- ("eligible", len(discovery.commits)),
+ ("snapshot_id", discovery_ev.payload.get("snapshot_id")),
+ ("config_digest", discovery_ev.payload.get("config_digest")),
+ ("observations", discovery_ev.payload.get("observation_count")),
+ ("eligible", discovery_ev.payload.get("eligible_count")),
+ ("event", _a(
+ _evidence_path("event", discovery_ev.event_id),
+ discovery_ev.event_id,
+ )),
]),
])
- no_comparisons_note = "No comparisons."
- if single_contributor:
- no_comparisons_note += " Single-contributor — no LLM judgments."
body.extend([
["h2", "commits"],
_link_list(commit_links),
@@ -2471,92 +2436,42 @@ async def epoch_detail(epoch: int):
@app.get("/commits/{commit_id}")
async def commit_detail(commit_id: str):
ev = find_evidence_payload("git.commit", "commit_id", commit_id)
- discovery, legacy_row = (None, None)
if not ev:
- discovery, legacy_row = _legacy_commit_row(commit_id)
- if not ev and not legacy_row:
- discovery, obs = _legacy_observation(commit_id)
- if obs is not None:
- epoch = discovery.epoch if discovery else "?"
- return _evidence_page(f"commit {commit_id[:24]}", [
- _evidence_nav(
- _a(_evidence_path("epoch", str(epoch)), f"epoch {epoch}")
- ),
- ["div.eyebrow", "commit"],
- ["h1", commit_id],
- ["p.note", "legacy evidence unavailable"],
- _dl_rows([
- ("oid", obs.get("oid")),
- ("eligible", obs.get("eligible")),
- ("exclusion_reason", obs.get("exclusion_reason")),
- ("epoch", str(epoch)),
- ]),
- ])
return _evidence_page("commit not found", [
_evidence_nav(),
["h1", "commit not found"],
["p", commit_id],
])
- if ev:
- p = ev.payload
- epoch = ev.epoch
- oid = p.get("oid", "")
- contributor = p.get("contributor", "")
- message = _blob_text(p.get("message"))
- patch = _blob_text(p.get("patch"))
- meta = _dl_rows([
+ p = ev.payload
+ epoch = ev.epoch
+ return _evidence_page(f"commit {commit_id[:24]}", [
+ _evidence_nav(_a(_evidence_path("epoch", str(epoch)), f"epoch {epoch}")),
+ ["div.eyebrow", "commit"],
+ ["h1", commit_id],
+ _dl_rows([
("commit_id", commit
… preview truncated; 7,279 characters omittedHardlinks — judgments / attempts / prompt
judgments
attempts
Prompt text is loaded only by the download route.