docs(adr): ADR 009 gives merged notices a covered range, one shared notice log, and a clock per process (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:23:49 -07:00
committed by Edward Langley
parent 5e94255c21
commit 353352f89d
+18 -11
View File
@@ -3,7 +3,7 @@
**Status:** proposed. ·
**Ticket:** improvise-felo.4 (the record of this ADR; implementation goes to its own
epic) ·
**Relates to:** ADR 003, ADR 005, ADR 007, ADR 008
**Relates to:** ADR 003, ADR 005, ADR 006, ADR 007, ADR 008
## Problem
@@ -25,7 +25,8 @@ equal today's eager results exactly.
A problem-agnostic workspace crate with no dependencies beyond `std` (so it is
wasm32-clean and serde-free). It provides:
- `ZSet<K>`, a map from `K: Ord` to a non-zero `i64` weight, which is an abelian group;
- a synchronous, single-threaded `Circuit`, with `delay`, `integrate`, `differentiate`,
- a synchronous, single-threaded `Circuit` per process, with `delay`, `integrate`,
`differentiate`,
`map`, `filter`, `flat_map`, `index_by`, the bilinear delta join `join_indexed`,
`distinct` and `aggregate`;
- resumable rebuilds: building an operator from a snapshot is a `step(deadline)`, so it
@@ -208,12 +209,18 @@ an **edge adapter**. So every edge has the same semantics:
- **Stamps compose end to end.** The host stamps each published batch (below), and an
intermediate edge forwards the stamp unchanged. So a loss anywhere on the path shows up
downstream as a gap.
- **One clock per process.** Each process runs its own synchronous sub-circuit. Across a
hop, the stamped batches are the clock.
- **Ordering and backpressure.** An edge delivers in order. A slow edge coalesces
exactly: deltas are summed, and notices merged.
exactly: deltas are summed, and notices merged. A merged notice carries the range of
publications it covers, `(from, to]`, so a downstream side sees no gap where nothing
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.
So each edge keeps an ordered log, every request can be retried, and a resync from a
publication stamp is idempotent.
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
of published notices**: a single log, not one per edge or client. A request older than
the log's start is answered "refetch".
- **Failure is local to a hop.** A hop may be a socket or a series of requests, and it is
alive while its next message or request succeeds. Each hop keeps alive in both
directions. A side that hears nothing within the timeout, or whose request fails,
@@ -221,7 +228,7 @@ an **edge adapter**. So every edge has the same semantics:
path, and the host answers from current state.
Aggregation stays on the model side (ADR 008), so the operators that aggregate run on
the host. Which other edges cross a wire is a deployment choice. The engine's
the model side, which may itself span a hop. Which other edges cross a wire is a deployment choice. The engine's
invalidation signal, which ADR 006's replay trigger relies on, is the change notice and
the refetch rule below, followed from a displayed cell's key back to the model.
@@ -260,13 +267,13 @@ A notice whose key lists exceed a fixed size carries "refetch" instead.
maintained key, so it maps member keys through its own remap first. It then refetches
what moved. It refetches everything when it sees a publication gap, a new
`document_generation` or host instance, its shape in the eviction list, or a notice it
cannot read. A slow client may merge pending notices: the union of keys, the latest
stamp, and axis changes kept in order. Because the wire is lossless, outputs keep the
cannot read. A slow client may merge pending notices: the union of keys, the covered
range of publications, and axis changes kept in order. Because the 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. `GridExtent` is derived by the client, and in records mode the record count is
state, only the shared notice log. `GridExtent` is derived by the client, and in records mode the record count is
fetched.
**Edge adapters at the client.** Toward the circuit, a client's adapter is a sink for
@@ -287,8 +294,8 @@ come out through clause 7's overlay. Numeric widths stay with ADR 008's view-lay
- **Rollback.** The circuit's state joins the snapshot `Host::mutating_frame` takes. An
inverse delta is not a rewind, because `delay` and `differentiate` keep history.
- **Memory.** Resident state beside the store is the per-cell `Seq`, the multisets,
`ExactSum` per summed group, occupancy per live shape, the successor-query index and
the width histograms. The store itself is the integrated base and is never copied. It
`ExactSum` per summed group, occupancy per live shape, the successor-query index, the
width histograms and the bounded notice log. The store itself is the integrated base and is never copied. It
is measured at ADR 008's fixture before the store-memory work begins.
## Alternatives rejected