From b26c92faf7e128b0a8197b259713af57fa395de6 Mon Sep 17 00:00:00 2001 From: "Claude (adr9-steward)" Date: Fri, 9 Oct 2026 22:35:01 -0700 Subject: [PATCH] docs(adr): ADR 009 states the model and view stores as conceptual boxes, not a network boundary (improvise-felo.4) Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze --- context/adr/009-incremental-evaluation.md | 33 +++++++++++++---------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/context/adr/009-incremental-evaluation.md b/context/adr/009-incremental-evaluation.md index 76611600..e08ba4b7 100644 --- a/context/adr/009-incremental-evaluation.md +++ b/context/adr/009-incremental-evaluation.md @@ -183,7 +183,8 @@ temporary overlay: - text counts the distinct keys left; - `Sum` subtracts exactly through the accumulator. -That is O(H log d) per read for every aggregate, with no per-client state. +That is O(H log d) per read for every aggregate, with no view-store state in the model +store. Aggregate-over-expression totals index their entries by row tuple for the same subtraction. @@ -206,6 +207,11 @@ in their last bits, because they become correctly rounded, and a sum of `-0.0` r ### 9. Delivery +The **model store** (the document model and the DBSP graph) and the **view store** (the +cursor, the window and unsaved view definitions) are conceptual boxes, not a network +boundary. Where a piece of state physically lives (a client, the host or a worker) is +an implementation detail. The model store's decisions depend only on the model. + **Every edge may cross a network.** An edge, meaning the stream from one operator to the next, may be in-process or remote. No edge assumes a function call or shared memory. An operator that sits at a wire boundary is just one implementation of an edge, @@ -264,12 +270,12 @@ an **edge adapter**. So every edge has the same semantics: position is a **watermark**: the whole stamp `(host instance, document_generation, publication)` up to which it has applied the stream, so everything at or before it is applied. A watermark from another host instance or generation is behind every current - stamp. The client holds one per shape key it reads. The host keeps no watermarks: a - client sends its own with each request. + stamp. The client holds one per shape key it reads. A watermark is view-store state: + the model store keeps none, and a client sends its own with each request. - where the shared log covers the watermark and the covered range holds only moved keys, the host sends the current values of those keys, restricted to the client's window, and the client applies `target − held` over them. The log holds keys and - flags, not values, so nothing per client is kept; + flags, not values, so the model store keeps no view-store state; - where the covered range also holds axis changes, relabels or flags, which positions and labels depend on, the correction carries them too, in order, as a merged notice does, so the client re-derives positions incrementally; @@ -292,7 +298,7 @@ relies on, is a correction trigger below, followed from a displayed cell's key b the model. **A request** is a shape key plus a window, given as an absolute cross-product top-left -and dimensions. The window is never state, and a request costs O(visible). +and dimensions. The window is view-store state, and a request costs O(visible). - **Positions** are bigints where they exceed 2⁵³, sent as JSON strings. - **Output** is keyed by tuple identity, with the position attached when sent. - **An item insert, removal or reorder** is sent as an axis change, and each client @@ -326,12 +332,11 @@ notice carries: **A client receives only what it needs.** Filtering is part of the DBSP graph, not a separate mechanism. A client either pulls just the state it needs, or pushes a filter to the host's side of its connection: a filter operator on its shape's query, written in the -query language, through which its notices pass. The filter never names the window, which -stays the client's: the client intersects the moved keys of a notice or correction with -its own window. A pushed filter belongs to the open -connection, like that connection's timeout, and is released with it. On a sequence of -requests the client sends it with each request, so the host keeps no per-client state -beyond an open connection. While a connection's filter is open, it counts as a request +query language, through which its notices pass. Whether the filter also carries the +client's window is an optimisation, since the model store's decisions depend only on the +model. A pushed filter belongs to the open connection, like that connection's timeout, +and is released with it. On a sequence of requests the client sends it with each +request. While a connection's filter is open, it counts as a request naming its shape, so its shape is not evicted. A notice whose key lists exceed a fixed size carries "correct" instead. That is a @@ -346,8 +351,8 @@ keys, the covered range of publications, and axis changes kept in order. Because wire is lossless, outputs keep the circuit's bit identity end to end. **What this replaces.** Today's per-session mirrors and pushed diffs (`Sessions`, -`ClientSession`, `grid_updates`, `Projection`, `Dirty`). The host keeps no per-client -state, only the shared notice log. `GridExtent` is derived by the client, and in records +`ClientSession`, `grid_updates`, `Projection`, `Dirty`). The model store keeps no +view-store state, only the shared notice log. `GridExtent` is derived by the client, and in records mode the record count is fetched. `Projection` also published the ADR 001 basis a write claims. Whether `project` returns it instead, and what replaces the layout tier, are ADR 008's questions. @@ -393,7 +398,7 @@ scheduling choice, made as an optimisation later. - The store scans and the per-view model clones go. - ADR 008 gets occupancy, totals and widths from one evaluator. - Live sources that report changes plug in. -- The host holds no per-client state. +- The model store holds no view-store state. **Bad:** - Two backends exist until `Eager` goes.