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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
This commit is contained in:
Claude (adr9-steward)
2026-10-09 22:35:01 -07:00
committed by Edward Langley
parent 14430b5628
commit b26c92faf7
+19 -14
View File
@@ -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.