From 353352f89d62e596f9c92dd46ce0cb715d7b7421 Mon Sep 17 00:00:00 2001 From: "Claude (adr9-steward)" Date: Fri, 9 Oct 2026 21:23:49 -0700 Subject: [PATCH] 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 Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze --- context/adr/009-incremental-evaluation.md | 29 ++++++++++++++--------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/context/adr/009-incremental-evaluation.md b/context/adr/009-incremental-evaluation.md index 44aeadcb..99dee2da 100644 --- a/context/adr/009-incremental-evaluation.md +++ b/context/adr/009-incremental-evaluation.md @@ -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`, 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