constitution · epochs · watch · epoch 3

comparison

c_b21d271b1b82 (tommy-mor) vs c_5cd3e5917d2f (tommy-mor)

download prompt · raw event · cmp_e7bdd46b854d64

council reasoning

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

Side A delivers a coherent, well-tested extensible resolver framework (GitHub on-demand import with cooldown/rate-limiting, DSL fenced-JSON body support) plus real bugfixes to URL identity semantics (stripping tracking query params, fixing broken external previews for blocked hosts like GitHub, YouTube canonicalization) backed by extensive unit and browser tests. Side B is a solid internal cleanup (removing EntityStore/EntityImported in favor of ephemeral TTL-evicted Reddit content) that reduces event-log bloat, but it is a narrower, single-purpose architecture fix with less new user-facing capability and fewer edge-case tests, despite Side A's diff containing some pure reformatting noise.

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

B makes a lasting architectural cut: drop EntityImported/EntityStore, log only NodeEnsured structure, and put Reddit display payloads in a TTL-evicted projection cache—correct durability/compliance vs permanent API JSON in the event log. A adds real value (query/fragment stripping, GitHub URL identity, on-demand resolver + SystemIngest + browser coverage), but much of the diff is rustfmt churn and UI wiring, and it still durably ingests fetched GitHub bodies the way B deliberately stopped doing for Reddit.

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

Side A introduces substantial new functionality: an extensible external URL foundation with GitHub-specific resolution, on-demand resolver UI/actions, durable system ingests, URL normalization improvements, depth-aware garden views, cooldown handling, and extensive tests. Side B makes a sound architectural change by removing persisted Reddit payloads in favor of ephemeral cached content with TTL eviction and simplifying the event log to structural events, but its impact is narrower than the broad new capabilities and infrastructure added in Side A.

sides

A — c_b21d271b1b82 (tommy-mor)

message

[06b48801] Implement extensible URL foundation (#145)

* Implement extensible URL foundation

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

* Make query params non-identity by default

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

* Fix external href helper test scope

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

* Avoid broken external previews for blocked hosts

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

* Clarify external empty state copy

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

* Add on-demand GitHub external resolver

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

* Add fenced JSON bodies for GitHub resolver

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

---------

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

diff preview

diff --git a/agents.md b/agents.md
index 59bd2174f4d2f972a45123fe10d408aa5881ee93..c66f33789441eea193b4354fce4c03b7fffdd639 100644
--- a/agents.md
+++ b/agents.md
@@ -55,6 +55,7 @@ Strict **CSP** that blocks `eval` would break the current app. Other projects ma
 | Ingests, grants, rooms, identity tokens, agent binds, redactions, etc. | **JSONL** | Appended in `server/src/api/rpc.rs`, `server/src/api/auth.rs` (and related paths) before updating `ReducerState` |
 | **`RoomMintInvite` links** | **RAM only** | `AppState.invites` — not appended as `InviteMinted` today; **lost on restart** (`server/src/state.rs`, `server/src/api/rpc.rs`). Event types `InviteMinted` / `InviteRedeemed` exist for replay and a possible future persisted mint (`server/src/reducer.rs`). |
 | **OAuth / pending sessions** | **RAM only** | `AppState.pending_sessions` (`server/src/state.rs`, `server/src/api/auth.rs`) |
+| **External resolver cooldowns** | **RAM only** | `AppState.resolver_runs` — debounce/rate-limit guard for on-demand resolver buttons. Resolver results themselves are durable synthetic `Ingest` events in `events.jsonl`. |
 | **Reducer projection** | **Derived** | Rebuilt from log on startup; not separately persisted |
 
 If you add a new ephemeral map or start persisting something that was RAM-only, **update this table and the code comments** (`server/src/state.rs` is a good anchor).
diff --git a/server/src/api/ui_html.rs b/server/src/api/ui_html.rs
index 7cbda3876451687aa7a55547fc06bbe86ac9d260..cd501ba6d4d656eabad03afed4583efdf885695d 100644
--- a/server/src/api/ui_html.rs
+++ b/server/src/api/ui_html.rs
@@ -18,14 +18,15 @@ use crate::{
         rpc::{rpc_post_redact, rpc_post_with_bearer, rpc_room_delete},
     },
     canonical_path::canonicalize_tag,
+    external_resolver::resolve_github_children,
+    html::vote_compare_post_success_js,
     html::{
-        fragment_new_thread_slot, login_to_post_hint_markup,
-        parse_html_ui_from_form, room_members_section_markup, thread_feed_html,
-        thread_feed_html_for_room, thread_feed_region_markup, thread_ui_collapse_redacted_post,
-        thread_ui_expand_post_full, thread_ui_expand_redacted_post, ui_js_warn, user_can_post_room,
-        user_can_view_room, HtmlUiAction, JsBuilder, ThreadNav,
+        fragment_new_thread_slot, login_to_post_hint_markup, parse_html_ui_from_form,
+        room_members_section_markup, thread_feed_html, thread_feed_html_for_room,
+        thread_feed_region_markup, thread_ui_collapse_redacted_post, thread_ui_expand_post_full,
+        thread_ui_expand_redacted_post, ui_js_warn, user_can_post_room, user_can_view_room,
+        HtmlUiAction, JsBuilder, ThreadNav,
     },
-    html::vote_compare_post_success_js,
     reducer::{scope_from_room_wire, ScopeId},
     state::AppState,
 };
@@ -104,26 +105,35 @@ async fn dispatch_ui_action(
                 )
                 .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()
-                }
+            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(),
+                Err((msg, hint)) => {
+                    form_js_error(error_target.as_ref(), &msg, hint.as_deref().unwrap_or(""))
+                        .into_response()
+                }
             }
         }
         HtmlUiAction::CheckIngest {
@@ -150,9 +160,19 @@ async fn dispatch_ui_action(
                 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(),
+                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::VoteComparePost {
@@ -167,7 +187,11 @@ async fn dispatch_ui_action(
             form_action,
         } => {
             if form_action != "/ui" {
-                return (StatusCode::BAD_REQUEST, "invalid vote_compare_post form_action").into_response();
+                return (
+                    StatusCode::BAD_REQUEST,
+                    "invalid vote_compare_post form_action",
+                )
+                    .into_response();
             }
             let Some(session) = session else {
                 return js_redirect("/login").into_response();
@@ -195,23 +219,15 @@ async fn dispatch_ui_action(
             let left_id = match crate::path_types::ItemId::parse(left_item.trim()) {
                 Some(i) => i.normalized_storage(),
                 None => {
-                    return form_js_error(
-                        err_tgt.as_ref(),
-                        "bad item",
-                        "Invalid left item path.",
-                    )
-                    .into_response();
+                    return form_js_error(err_tgt.as_ref(), "bad item", "Invalid left item path.")
+                        .into_response();
                 }
             };
             let right_id = match crate::path_types::ItemId::parse(right_item.trim()) {
                 Some(i) => i.normalized_storage(),
                 None => {
-                    return form_js_error(
-                        err_tgt.as_ref(),
-                        "bad item",
-                        "Invalid right item path.",
-                    )
-                    .into_response();
+                    return form_js_error(err_tgt.as_ref(), "bad item", "Invalid right item path.")
+                        .into_response();
                 }
             };
             let mut rl = ratio_left.trim().parse::<i32>().unwrap_or(0).max(0);
@@ -231,7 +247,15 @@ async fn dispatch_ui_action(
                 right_id.as_str()
             );
 
-            match rpc_post_with_bearer(state, &session.bearer, room.clone(), thread_tag.clone(), text).await {
+            match rpc_post_with_bearer(
+                state,
+                &session.bearer,
+                room.clone(),
+                thread_tag.clone(),
+                text,
+            )
+            .await
+            {
                 Ok(RpcResult::PostOk {
                     post_id,
                     post_index,
@@ -277,7 +301,10 @@ async fn dispatch_ui_action(
                     "Post did not return PostOk.",
                 )
                 .into_response(),
-                Err((msg, hint)) => form_js_error(err_tgt.as_ref(), &msg, hint.as_deref().unwrap_or("")).into_response(),
+                Err((msg, hint)) => {
+                    form_js_error(err_tgt.as_ref(), &msg, hint.as_deref().unwrap_or(""))
+                        .into_response()
+                }
             }
         }
         HtmlUiAction::SetGardenPin {
@@ -288,7 +315,11 @@ async fn dispatch_ui_action(
             form_action,
         } => {
             if form_action != "/ui" {
-                return (StatusCode::BAD_REQUEST, "invalid set_garden_pin form_action").into_response();
+                return (
+                    StatusCode::BAD_REQUEST,
+                    "invalid set_garden_pin form_action",
+                )
+                    .into_response();
             }
             let next_path = sanitize_garden_pin_next(&next);
             use crate::html::{encode_pin_cookie_value, GARDEN_PIN_COOKIE};
@@ -301,7 +332,11 @@ async fn dispatch_ui_action(
             if room.is_empty() {
                 return (StatusCode::BAD_REQUEST, "missing room").into_response();
             }
-            let Some(raw) = item_storage.as_ref().map(|s| s.trim().to_string()).filter(|s| !s.is_empty()) else {
+            let Some(raw) = item_storage
+                .as_ref()
+                .map(|s| s.trim().to_string())
+                .filter(|s| !s.is_empty())
+            else {
                 return (StatusCode::BAD_REQUEST, "missing item").into_response();
             };
             let Some(item) = ItemId::parse(&raw) else {
@@ -309,16 +344,65 @@ async fn dispatch_ui_action(
             };
             let item = item.normalized_storage();
             let val = encode_pin_cookie_value(&room, item.as_str());
-            let cookie = format!("{GARDEN_PIN_COOKIE}={val}; Path=/; SameSite=Lax; Max-Age=7776000");
+            let cookie =
+                format!("{GARDEN_PIN_COOKIE}={val}; Path=/; SameSite=Lax; Max-Age=7776000");
             redirect_with_pin_cookie(&cookie, &next_path)
         }
+        HtmlUiAction::ResolveExternal {
+            room_wire,
+            item_storage,
+            mode,
+            next,
+            form_action,
+        } => {
+            if form_action != "/ui" {
+                return (
+                    StatusCode::BAD_REQUEST,
+                    "invalid resolve_external form_action",
+                )
+                    .into_response();
+            }
+            let Some(session) = session else {
+                return js_redirect("/login").into_response();
+            };
+            let room = room_wire.trim();
+            if room.is_empty() {
+                return ui_js_warn("missing room").into_response();
+            }
+            let reduced = state.reduced.read().await;
+            if matches!(scope_from_room_wire(room), ScopeId::Room(_)) {
+                if !user_can_post_room(&reduced, room, &session.username) {
+                    drop(reduced);
+                    return ui_js_warn("forbidden").into_response();
+                }
+            }
+            drop(reduced);
+
+            let Some(item) = crate::path_types::ItemId::parse(item_storage.trim()) else {
+                return ui_js_warn("bad item").into_response();
+            };
+            let target = if mode.trim() == "siblings" {
+                m

… preview truncated; 74,788 characters omitted

download full diff A

B — c_5cd3e5917d2f (tommy-mor)

message

[1d14ff09] Ephemeral Reddit content; log structure only (#46)

* Keep Reddit content ephemeral; log structure only

Remove EntityImported and EntityStore. Reddit fetches write display
content directly to the projection with a fetched_at timestamp, while
the event log records NodeEnsured for discovered identities only.

A background task evicts cached display content after 48 hours. Votes,
tree structure, and ItemIds remain in the log and projection.

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

* Fix reddit import test assertions and Clojure syntax

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

---------

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

diff preview

diff --git a/server/src/bin/storage_bench.rs b/server/src/bin/storage_bench.rs
index 4762d0d23bc8e2df439afe09a3235ba7f72bd486..3d988416ad36b27a7d3dc84280cfdbdcafa43e69 100644
--- a/server/src/bin/storage_bench.rs
+++ b/server/src/bin/storage_bench.rs
@@ -6,8 +6,8 @@ use std::{
 };
 
 use sorter2_server::{
-    entity_store::EntityStore, event_log::EventLog, events::Event, journal::JournalClient,
-    projection_apply, projection_store::ProjectionStore,
+    event_log::EventLog, events::Event, journal::JournalClient, projection_apply,
+    projection_store::ProjectionStore,
 };
 
 #[tokio::main]
@@ -18,12 +18,10 @@ async fn main() -> Result<(), Box<dyn Error + Send + Sync>> {
     let data_dir = opts.data_dir.to_string_lossy().into_owned();
     let event_log = Arc::new(EventLog::new(format!("{data_dir}/events.jsonl")));
     let db = durable::Db::open(opts.data_dir.join("store"))?;
-    let entity_store = EntityStore::from_db(&db)?;
     let projection_store = ProjectionStore::from_db(&db)?;
 
     let journal = JournalClient::spawn(
         event_log.clone(),
-        entity_store.clone(),
         projection_store.clone(),
         event_log.last_sequence().await? + 1,
     );
@@ -46,12 +44,9 @@ async fn main() -> Result<(), Box<dyn Error + Send + Sync>> {
     drop(journal);
 
     let rebuild_start = Instant::now();
-    entity_store.reset()?;
     projection_store.reset()?;
     let rebuild = event_log
-        .replay(|record| {
-            projection_apply::apply_records(&projection_store, &entity_store, &[record])
-        })
+        .replay(|record| projection_apply::apply_records(&projection_store, &[record]))
         .await?;
     let rebuild_elapsed = rebuild_start.elapsed();
 
diff --git a/server/src/entity_store.rs b/server/src/entity_store.rs
deleted file mode 100644
index d29f39aecca6f12cdcf263cf77c3654eb4ee6cfa..0000000000000000000000000000000000000000
--- a/server/src/entity_store.rs
+++ /dev/null
@@ -1,134 +0,0 @@
-//! Off-heap storage for full entity payloads (Reddit API JSON).
-//!
-//! Derived [`crate::reducer::EntityData`] is stored on the node; the raw JSON
-//! lives here, in the shared durable [`Store`] schema.
-
-use std::path::Path;
-
-use durable::{Batch, Db, Durability};
-use serde_json::Value;
-
-use crate::{
-    path_types::ItemId,
-    storage_dto::{decode_entity_payload, encode_entity_payload},
-    storage_schema::{Store, StoreFields},
-};
-
-const ENTITY_SCHEMA_KEY: &str = "schema_version";
-const ENTITY_SCHEMA_VERSION: u64 = 2;
-
-#[derive(Debug, thiserror::Error)]
-pub enum EntityStoreError {
-    #[error("durable error: {0}")]
-    Durable(#[from] durable::Error),
-    #[error("json error: {0}")]
-    Json(#[from] serde_json::Error),
-    #[error("storage decode error: {0}")]
-    Storage(String),
-    #[error("io error: {0}")]
-    Io(#[from] std::io::Error),
-}
-
-/// Disk-backed map of entity id → raw JSON payload.
-#[derive(Clone)]
-pub struct EntityStore {
-    db: Db,
-}
-
-impl EntityStore {
-    /// Open (or create) the entity database under `dir`.
-    pub fn open(dir: &Path) -> Result<Self, EntityStoreError> {
-        std::fs::create_dir_all(dir)?;
-        let db = Db::open(dir)?;
-        Self::from_db(&db)
-    }
-
-    /// Create an entity store backed by an already-open database.
-    pub fn from_db(db: &Db) -> Result<Self, EntityStoreError> {
-        let store = Self { db: db.clone() };
-        let version = Store::root()
-            .entity_meta()
-            .key(&ENTITY_SCHEMA_KEY.to_string())
-            .get(db)?;
-        if version != Some(ENTITY_SCHEMA_VERSION) {
-            store.reset()?;
-        }
-        Ok(store)
-    }
-
-    /// Clear rebuildable entity payloads and reset storage schema metadata.
-    pub fn reset(&self) -> Result<(), EntityStoreError> {
-        let root = Store::root();
-        self.db.apply(
-            &[root.entities().clear(), root.entity_meta().clear()],
-            Durability::SyncWal,
-        )?;
-        self.db.run(
-            root.entity_meta()
-                .key(&ENTITY_SCHEMA_KEY.to_string())
-                .set(&ENTITY_SCHEMA_VERSION),
-            Durability::SyncWal,
-        )?;
-        Ok(())
-    }
-
-    /// Persist a payload for `id` (overwrites any existing entry).
-    pub fn put(&self, id: &ItemId, payload: &Value) -> Result<(), EntityStoreError> {
-        self.db.run(
-            Store::root()
-                .entities()
-                .key(&id.as_str().to_string())
-                .set(&encode_entity_payload(payload)),
-            Durability::SyncWal,
-        )?;
-        Ok(())
-    }
-
-    /// Add a payload write to the caller's batch.
-    pub fn put_in_batch(
-        &self,
-        batch: &mut Batch,
-        id: &ItemId,
-        payload: &Value,
-    ) -> Result<(), EntityStoreError> {
-        batch.write(
-            Store::root()
-                .entities()
-                .key(&id.as_str().to_string())
-                .set(&encode_entity_payload(payload)),
-        );
-        Ok(())
-    }
-
-    /// Load a stored payload, if present.
-    pub fn get(&self, id: &ItemId) -> Result<Option<Value>, EntityStoreError> {
-        match Store::root()
-            .entities()
-            .key(&id.as_str().to_string())
-            .get(&self.db)?
-        {
-            Some(record) => decode_entity_payload(record)
-                .map(Some)
-                .map_err(EntityStoreError::Storage),
-            None => Ok(None),
-        }
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use serde_json::json;
-
-    #[test]
-    fn round_trip_payload() {
-        let tmp = tempfile::tempdir().unwrap();
-        let store = EntityStore::open(tmp.path()).unwrap();
-        let id = ItemId::from_url("https://reddit.com/r/rust").unwrap();
-        let payload = json!({"kind": "t5", "data": {"display_name": "rust"}});
-
-        store.put(&id, &payload).unwrap();
-        let loaded = store.get(&id).unwrap().unwrap();
-        assert_eq!(loaded, payload);
-    }
-}
diff --git a/server/src/events.rs b/server/src/events.rs
index a3d88e285c645a96d33e6f0aed1b487b843aceb8..d76c3bb4277216b0d39c9422ba7a50db10a95e05 100644
--- a/server/src/events.rs
+++ b/server/src/events.rs
@@ -1,5 +1,4 @@
 use serde::{Deserialize, Serialize};
-use serde_json::Value;
 
 /// Schema version for JSONL log records. Bump when event semantics change.
 pub const CURRENT_LOG_SCHEMA: u32 = 1;
@@ -30,7 +29,7 @@ pub type ViewRecord = LogRecord<ViewEvent>;
 /// Wall-clock timestamp carried on the log envelope for domain events.
 pub fn event_timestamp(event: &Event) -> i64 {
     match event {
-        Event::VoteRecorded { ts, .. } | Event::EntityImported { ts, .. } => *ts,
+        Event::VoteRecorded { ts, .. } => *ts,
         Event::NodeEnsured { .. } => crate::fetch::now_ms(),
     }
 }
@@ -57,6 +56,4 @@ pub enum Event {
     },
     /// Register a node path in the fractal tree (no external fetch).
     NodeEnsured { id: String },
-    /// Full upstream API payload for a node (domain-specific view derived at replay/render time).
-    EntityImported { id: String, ts: i64, payload: Value },
 }
diff --git a/server/src/journal.rs b/server/src/journal.rs
index 50bc89f976edb82b7b0e49e954a8eccbbe82bf87..d50023aca7c3e74068de988449b11baee32d1ecf 100644
--- a/server/src/journal.rs
+++ b/server/src/journal.rs
@@ -5,7 +5,6 @@ use std::sync::Arc;
 use tokio::sync::{mpsc, oneshot};
 
 use crate::{
-    entity_store::EntityStore,
     event_log::EventLog,
     events::{event_timestamp, Event, EventRecord},
     projection_apply,
@@ -25,7 +24,6 @@ pub struct JournalClient {
 impl JournalClient {
     pub fn spawn(
         event_log: Arc<EventLog>,
-        entity_store: EntityStore,
         projection_store: ProjectionStore,
         next_seq: u64,
     ) -> Self {
@@ -33,7 +31,6 @@ impl JournalClient {
         tokio::spawn(journal_worker(
             rx,
             event_log,
-            entity_store,
             projection_store,
             next_seq,
         ));
@@ -62,7 +59,6 @@ impl JournalClient {
 async fn journal_worker(
     mut rx: mpsc::Receiver<JournalCommand>,
     event_log: Arc<EventLog>,
-    entity_store: EntityStore,
     projection_store: ProjectionStore,
     mut next_seq: u64,
 ) {
@@ -75,7 +71,6 @@ async fn journal_worker(
         let result = append_and_project_batch(
             &event_log,
             &projection_store,
-            &entity_store,
             &mut next_seq,
             &batch,
         )
@@ -99,7 +94,6 @@ async fn journal_worker(
 async fn append_and_project_batch(
     event_log: &EventLog,
     projection_store: &ProjectionStore,
-    entity_store: &EntityStore,
     next_seq: &mut u64,
     commands: &[JournalCommand],
 ) -> Result<(), String> {
@@ -117,7 +111,7 @@ async fn append_and_project_batch(
         .await
         .map_err(|e| e.to_string())?;
     *next_seq = seq;
-    projection_apply::apply_records(projection_store, entity_store, &records)
+    projection_apply::apply_records(projection_store, &records)
         .map_err(|e| format!("projection apply failed after durable append: {e}"))
 }
 
@@ -132,10 +126,9 @@ mod tests {
         let log_path = tmp.path().join("events.jsonl");
         let event_log = Arc::new(EventLog::new(log_path));
         let db = durable::Db::open(tmp.path().join("store")).unwrap();
-        let entity_store = EntityStore::from_db(&db).unwrap();
         let projection_store = ProjectionStore::from_db(&db).unwrap();
 
-        let journal = JournalClient::spawn(event_log, entity_store, projection_store.clone(), 1);
+        let journal = JournalClient::spawn(event_log, projection_store.clone(), 1);
 
         let j1 = journal.clone();
         let j2 = journal.clone();
@@ -177,11 +170,9 @@ mod tests {
             .unwrap();
 
         let db = durable::Db::open(tmp.path().join("store")).unwrap();
-        let entity_store = EntityStore::from_db(&db).unwrap();
         let projection_store = ProjectionStore::from_db(&db).unwrap();
         projection_apply::apply_records(
             &projection_store,
-            &entity_store,
             &[EventRecord::new(
                 1,
                 1,
@@ -196,7 +187,6 @@ mod tests {
 
         let journal = JournalClient::spawn(
             event_log.clone(),
-            entity_store,
             projection_store.clone(),
             next_seq,
         );
@@ -219,10 +209,9 @@ mod tests {
         let log_path = tmp.path().join("events.jsonl");
         let event_log = Arc::new(EventLog::new(log_path));
         let db = durable::Db::open(tmp.path().join("store")).unwrap();
-        let entity_store = EntityStore::from_db(&db).unwrap();
         let projection_store = ProjectionStore::from_db(&db).unwrap();
         let journal =
-            JournalClient::spawn(event_log.clone(), entity_store, projection_store.clone(), 1);
+            JournalClient::spawn(event_log.clone(), projection_store.clone(), 1);
 
         journal
             .append_many(vec![
diff --git a/server/src/lib.rs b/server/src/lib.rs
index 5c02c8e704e4664453bad75d819df8a067668176..3dfc7c8acb8ed61bb73ade63e72768e402042cc5 100644
--- a/server/src/lib.rs
+++ b/server/src/lib.rs
@@ -1,5 +1,4 @@
 pub mod api;
-pub mod entity_store;
 pub mod event_log;
 pub mod events;
 pub mod fetch;
diff --git a/server/src/projection_apply.rs b/server/src/projection_apply.rs
index 5644557a41b3e9497c7421b444155ae629fa79f1..9c8990a8af927f35d3344c8d0872a516aba56b86 100644
--- a/server/src/projection_apply.rs
+++ b/server/src/projection_apply.rs
@@ -1,22 +1,20 @@
 //! Apply event-log records to the durable projection as precise point updates.
 //!
 //! Each batch of records lowers to reified durable writes (edge merges, child
-//! links, voted-pair flags, recent-vote pushes, entity payloads) plus a cursor
-//! advance, all committed in one atomic `DisableWal` batch. The cursor moving in
-//! the same batch as the (non-idempotent) edge merges guarantees e

… preview truncated; 33,427 characters omitted

download full diff B

Hardlinks — judgments / attempts / prompt

prompt download

judgments

attempts

Prompt text is loaded only by the download route.