docs(adr): ADR 009 makes an edge a session rather than a two-hop path, and its clock causal (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:
committed by
Edward Langley
parent
e720f7ca50
commit
63ee13289b
@@ -157,7 +157,8 @@ changes no operator. The visible axis is a remap.
|
||||
These are the only rebuilds; every other change is a delta.
|
||||
- **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).
|
||||
it. The period is a local duration on the host, set by measurement, and never compared
|
||||
with another machine's clock. Every eviction is announced (clause 9).
|
||||
|
||||
**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
|
||||
@@ -193,8 +194,7 @@ in their last bits, because they become correctly rounded, and a sum of `-0.0` r
|
||||
### 9. Delivery
|
||||
|
||||
**Every edge may cross a network.** An edge, meaning the stream from one operator to the
|
||||
next, may be in-process or remote, and a path may cross up to two network hops: browser ⇄
|
||||
gateway ⇄ host, or host ⇄ worker ⇄ client. No edge assumes a function call or shared
|
||||
next, may be in-process or remote. No edge assumes a function call or shared
|
||||
memory. An operator that sits at a wire boundary is just one implementation of an edge,
|
||||
an **edge adapter**. So every edge has the same semantics:
|
||||
- **A wire form.** Every element type that flows on an edge has a serializable form,
|
||||
@@ -218,27 +218,30 @@ an **edge adapter**. So every edge has the same semantics:
|
||||
every element type has a round-trip property test, where encoding then decoding is
|
||||
the identity for every value, including `-0.0`, NaN payloads, 28-digit decimals and
|
||||
`i128`-wide fractions.
|
||||
- **Stamps compose end to end.** The host stamps each committed command (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 commands are the clock. A delta crosses a hop only after its command
|
||||
- **The clock is causal, never a wall clock.** Ordering, staleness and loss detection
|
||||
use only the stamp sequence (below), a logical clock the host advances once per
|
||||
committed command. Each process runs its own synchronous sub-circuit, and across an
|
||||
edge the stamped commands are the clock. No decision compares wall-clock times across
|
||||
machines, so clock skew cannot affect it. A delta crosses a wire only after its command
|
||||
commits, so a remote operator never sees a delta that is later rolled back.
|
||||
- **Ordering and backpressure.** An edge delivers in order. A slow edge coalesces
|
||||
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.
|
||||
Every request can be retried, and a resync from a watermark is idempotent. A client
|
||||
publications it covers, `(from, to]`, so the client that merges notices sees no gap
|
||||
where nothing was lost.
|
||||
- **An edge is a session, not a connection.** An edge may be one long-lived connection,
|
||||
or a sequence of requests carrying the same session token or cookie, for example
|
||||
several HTTP requests. The token only lets a client's requests be recognised as one
|
||||
edge: the host keeps no per-client state, so what continues across connections is the
|
||||
client's own watermark and shape keys. 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).
|
||||
- **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,
|
||||
treats the hop as dropped. The downstream side then asks for a correction from its
|
||||
- **Failure.** An edge is alive while its next message or request succeeds, and each end
|
||||
keeps alive in both directions. Timeouts are local monotonic durations, never compared
|
||||
across machines. A side that hears nothing within its timeout, or whose request fails,
|
||||
treats the connection as dropped. The client then asks for a correction from its
|
||||
watermark.
|
||||
|
||||
- **Correction is Z-set arithmetic.** A correction is `target − held`, where `target`
|
||||
@@ -248,8 +251,8 @@ an **edge adapter**. So every edge has the same semantics:
|
||||
position is a **watermark**: the whole stamp `(host instance, document_generation,
|
||||
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. The host and any
|
||||
gateway keep no watermarks: a client sends its own with each request.
|
||||
stamp. The client holds one per shape key it reads. The host keeps no watermarks: a
|
||||
client sends its own with each request.
|
||||
- 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
|
||||
@@ -266,12 +269,11 @@ an **edge adapter**. So every edge has the same semantics:
|
||||
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.
|
||||
weights, in the lossless wire form, so a client and a worker apply 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
|
||||
deployment choice. The engine's invalidation signal, which ADR 006's replay trigger
|
||||
the model side. Which other edges cross a wire is a deployment choice. The engine's invalidation signal, which ADR 006's replay trigger
|
||||
relies on, is a correction trigger below, followed from a displayed cell's key back to
|
||||
the model.
|
||||
|
||||
@@ -313,7 +315,7 @@ fallback, and it costs every reader a correction.
|
||||
maintained key, so it maps member keys through its own remap first. It then fetches what
|
||||
moved. It asks for a correction when it sees a publication gap, a new
|
||||
`document_generation` or host instance, its shape in the eviction list, a notice it
|
||||
cannot read, or a dropped hop. A slow client may merge pending notices: the union of
|
||||
cannot read, or a dropped connection or failed request. 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.
|
||||
|
||||
@@ -324,12 +326,10 @@ mode the record count is fetched. `Projection` also published the ADR 001 basis
|
||||
claims. Whether `project` returns it instead, and what replaces the layout tier, are
|
||||
ADR 008's questions.
|
||||
|
||||
**Edge adapters at the client.** Toward the circuit, a client's adapter is a sink for
|
||||
results and notices and a source of requests. Toward the client it is the mirror image.
|
||||
Each hop has an adapter at each end, and each end keeps only that hop's state: its
|
||||
liveness timer, and at the client end the watermark. A gateway hop (browser ⇄ gateway ⇄
|
||||
host) may serve many clients behind one upstream connection, so the host sends one notice
|
||||
per gateway rather than one per client. In process, an edge is a function call.
|
||||
**Edge adapters.** An edge that crosses a wire has one adapter at each end. Toward the
|
||||
circuit, the client's adapter is a sink for results and notices and a source of requests.
|
||||
Toward the client it is the mirror image. Each end keeps only its timeout, and the
|
||||
client's end keeps the watermark. In process, an edge is a function call.
|
||||
|
||||
### 10. Text column widths
|
||||
|
||||
@@ -397,7 +397,8 @@ Step 8's required tests:
|
||||
random edit sequences and losses);
|
||||
- a refused command consumes no publication number, and the commands before it in its
|
||||
frame stay published;
|
||||
- a dropped hop triggers a correction.
|
||||
- a dropped connection or a failed request triggers a correction, and a later request
|
||||
with the same session resumes from the watermark.
|
||||
|
||||
## Open questions
|
||||
|
||||
@@ -406,7 +407,7 @@ Step 8's required tests:
|
||||
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.
|
||||
backend may hold?
|
||||
*Recommendation:* broadcast with the size cap. Add topics only if measured traffic
|
||||
with many clients on different shapes justifies them.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user