docs(adr): ADR 009 reads only committed state and sends a whole window when a range moved positions (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 21:35:35 -07:00
committed by Edward Langley
parent 5239d3b95a
commit 13b3dff35e
+25 -19
View File
@@ -140,18 +140,19 @@ aggregates are keyed by axis **items**, so a collapse, hide, show, reorder or ad
changes no operator. The visible axis is a remap.
- **A new key** builds its operators as a sliced job under ADR 008's job runner. Deltas
arriving during the build are buffered and added at install.
arriving during the build are buffered and added at install. The finished result is the
response to the request that asked for it, not a notice.
- **A formula edit** is a formula delta fed to the circuit.
- **A document swap** rebuilds everything.
- **Saving a view definition** touches no operator.
- **Live keys are a cache.** A key is evicted after a period in which no request names
it (the period is set by measurement), and every eviction is announced (clause 9).
**Column totals and hidden rows.** This holds if hidden items travel in a request, which
is ADR 008's open question. If they do not, totals are over all rows. A total pools only
the drawn rows, so a hidden row leaves it. The shape key is shared and a hide is one
client's, so totals are kept over all rows. A read subtracts the request's H hidden cells
as a temporary overlay:
**Column totals and hidden rows.** Totals are kept over all rows, because the shape key
is shared and a hide is one client's. Whether hidden items travel in a request is ADR
008's open question. If they do not, a total is read as it is kept. If they do, a total
must leave the hidden rows out, so a read subtracts the request's H hidden cells as a
temporary overlay:
- `Count` subtracts;
- `Min` and `Max` skip keys whose whole count is hidden;
- text counts the distinct keys left;
@@ -218,8 +219,8 @@ an **edge adapter**. So every edge has the same semantics:
was lost.
- **No long-lived stream assumed.** An edge may be carried as a series of requests and
responses, for example several HTTP requests on one connection, as well as a socket.
Every request can be retried, and a resync from a publication stamp is idempotent. A
client asks for "notices since n", and the host answers from **one shared, bounded log
Every request can be retried, and a resync from a watermark is idempotent. A client
asks for "notices after my watermark", and the host answers from **one shared, bounded log
of published notices**: a single log, not one per edge or client, holding the notices
themselves (keys and flags, not values). A request older than the log's start is
answered with a correction (below).
@@ -238,18 +239,23 @@ an **edge adapter**. So every edge has the same semantics:
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 and any
gateway keep no watermarks: a client sends its own with each request.
- where the shared log covers the watermark, the host sends the current values of the
keys the log lists as moved since then, restricted to the client's window, and the
client applies `target − held` over those keys. The log holds keys and flags; values
are always read from the model's current state, so nothing per client is kept;
- otherwise the host sends `target` for the whole window, and the client applies
`target − held`;
- 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;
- where the covered range holds an axis change, a relabel or a flag, which position
and labels depend on, the host sends `target` for the whole window;
- where the log no longer covers the watermark, the host sends `target` for the whole
window, and the client applies `target − held`;
- a full refetch is `target − ∅`.
A correction is labelled with the publication range it covers, so it is applied only at
the watermark it starts from, which makes it idempotent and resumable. Its elements are
`(key, value)` pairs with weights, in the lossless wire form, so a client, a gateway or a
worker applies it with the same code.
**Every read sees only the last committed state**, whether it is a correction, a
fetch or `project`, and it is labelled with that state's stamp. A value a failed
command wrote and then rolled back is never read. A correction is labelled with the
publication range it covers, so it is applied only at the watermark it starts from,
which makes it idempotent and resumable. Its elements are `(key, value)` pairs with
weights, in the lossless wire form, so a client, a gateway or a worker applies it with
the same code.
Aggregation stays on the model side (ADR 008), so the operators that aggregate run on
the model side, which may itself span a hop. Which other edges cross a wire is a
@@ -384,7 +390,7 @@ Step 8's required tests:
1. **Should notices be filtered per client?** Clause 9 sends every client every live
shape's moved keys. That keeps the host free of per-client state, but traffic is
O(clients × all moved keys) per batch. Filtering by topic would send each connection
O(clients × all moved keys) per committed command. Filtering by topic would send each connection
only its own shape's key lists. It needs one subscription per connection, which pins
its shape in the cache. Is such a subscription "model-relevant information" the
backend may hold? On a two-hop path the topics could instead live in a gateway.