From d61f5d74e6e8a7e23c3586d92d7fbe72e61748c0 Mon Sep 17 00:00:00 2001 From: "Claude (adr9-steward)" Date: Fri, 9 Oct 2026 22:36:04 -0700 Subject: [PATCH] docs(adr): ADR 009 permits disposable server-side twins of client state at an edge (improvise-felo.4) Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze --- context/adr/009-incremental-evaluation.md | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/context/adr/009-incremental-evaluation.md b/context/adr/009-incremental-evaluation.md index e08ba4b7..1ab6966a 100644 --- a/context/adr/009-incremental-evaluation.md +++ b/context/adr/009-incremental-evaluation.md @@ -210,7 +210,11 @@ in their last bits, because they become correctly rounded, and a sum of `-0.0` r 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. +an implementation detail. The model store's decisions depend only on the model. A +server-side twin of a client's state, such as its filter, watermark or window, may be +held at the serving end of an edge where that makes the graph genuinely simpler. It is +then part of the edge, not of the model store, and it is disposable: losing it forces a +correction from the client's watermark, never wrong data. **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 @@ -258,8 +262,8 @@ an **edge adapter**. So every edge has the same semantics: themselves (keys and flags, not values). A request older than the log's start is answered with a correction (below). - **Failure.** Timeouts are local monotonic durations, never compared across machines. - On a long-lived connection, both ends keep alive, and a host-side timeout belongs to - that open connection and is released with it. On a sequence of requests, only the + On a long-lived connection, both ends keep alive, and a host-side timeout is tied to + that open connection. On a sequence of requests, only the client detects failure: a request that fails or times out. In either case the client then asks for a correction from its watermark. @@ -271,7 +275,8 @@ an **edge adapter**. So every edge has the same semantics: 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. A watermark is view-store state: - the model store keeps none, and a client sends its own with each request. + the model store keeps none, so a client sends its own with each request unless a twin + at the serving end holds it. - 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 @@ -334,9 +339,8 @@ separate mechanism. A client either pulls just the state it needs, or pushes a f 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. 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 +model. A pushed filter is held by the open connection, or by a twin. Otherwise 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