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