docs(chord): slice 6b D4, the store has Durable's Storage semantics

This commit is contained in:
Mario Zechner 2026-10-28 11:18:41 +00:00
parent cc12dad236
commit a9181321a0
3 changed files with 32 additions and 18 deletions

View file

@ -1648,6 +1648,17 @@ validation and copies the v2 kernel added around Delta.
"document ids monotonic with seq". Durable's own ids still rise in its creation order (its Session line serializes
its commits and mints inside them), which its scans, transcripts and windows rely on; Chord's ids are gaps between
them, as the five record types already leave. A minted id never committed may come again after a reopen (S10).
- **Slice 6b D4: the store has Durable's Storage semantics (owner, 2026-10-27: "agreed"; supersedes in part "Store
errors: Durable's rule" and slice-6a rule 10's "an expression result outside it fails the step, which is
`storage_failed`"):** Durable has two levels: its `Storage` rolls a failed commit back and stays usable (the
conformance suite tests it), its Session fails on any commit error (`GuardedStorage`). "Store errors: Durable's
rule" had put the Session's level into the store. Now: a step that fails before `COMMIT` rolls back and rejects that
commit only (the step's code runs in the transaction callback, so an error there is known to come before `COMMIT`);
the store stops only when the outcome is unknown (`COMMIT` or `ROLLBACK` failed), when `adopt` threw, or on a read's
backend error. Owners react as Durable's Session: Durable's Session fails (unchanged); a Chord object whose commit the
step refused fails live (`object_failed`, `broken`: memory and disk disagree); an `int` `CHECK` failure (a plugin's
value out of range) fails only that transaction (`invalid/args`). `StoreStorage` runs Durable's commit checks in the
step, where `SqliteStorage` runs them: no special case. Built in 6b (the store's step and object failure).
- **The performance and memory bar, and converging reviews (owner, 2026-10-27: "yes"):** supersedes "as good as
HEAD in everything" for Chord v2 slices and pi changes. CPU: each hot path (calls, changes, watches, links) within
about 5% of HEAD, measured in bursts with alternating processes pinned to one CPU; worse is fixed or goes to the

View file

@ -894,9 +894,9 @@ host.serve(DraftStore, (_, self) => { // local, empty params: may
storage batch through the store's commit step, `await store.commit(writes, adopt, ctx)` without the caller's abort
signal (deltas, or a base when `checkpointWhen` says so; the line held until the step returned, as Durable's
`#runCommit`, owner, 2026-10-26); adopt by pointer swap and mark dependents in the step, persisted and in-memory
documents and staged changes in one `host.batch.commit` (any backend error in the step is `storage_failed`,
Durable's rule, owner, 2026-10-26: plugin mistakes are refused when a write is staged; a seal-fenced commit is
rejected `released` before `BEGIN`); then the owner's synchronous flush recomputes dirty derives, sends one message
documents and staged changes in one `host.batch.commit` (errors as Durable's Storage, owner, 2026-10-27: a step that
fails before `COMMIT` rolls back and rejects that commit only, nothing adopted, the store goes on; plugin mistakes are
refused when a write is staged; a seal-fenced commit is rejected `released` before `BEGIN`); then the owner's synchronous flush recomputes dirty derives, sends one message
per link and queues watch callbacks (W1); drafts then throw if touched. In-memory-only commits take no store seq and never
enter the commit queue (W12). No
revalidation of the whole document, no copy, no freeze.
@ -1077,10 +1077,15 @@ host.serve(DraftStore, (_, self) => { // local, empty params: may
runtime has it, otherwise a `MessageChannel` message (2026-10-27, slice 6a build reviews).
- **Eviction as in Durable,** for keyed persisted documents only (persisted singletons and `at(key)` documents stay
in memory while their object lives: `handle.value` is a synchronous read; owner, 2026-10-26): one not read, written or watched for the retention
period (default 10 minutes) leaves memory and reloads on the next access. A storage error ends the store and closes
the host; `onError` receives `{ kind: "storage_failed" }` and the app decides (section 3). Any backend error in a
commit step, or on a read whose `ctx` was not aborted, is a storage error (Durable's rule); later calls reject
`unavailable/storage_failed`.
period (default 10 minutes) leaves memory and reloads on the next access. **Store errors (owner, 2026-10-27; supersedes in part "Store errors: Durable's rule"):** the store has Durable's
`Storage` semantics, its owners react as Durable's Session does. A step that fails before `COMMIT` rolls back: that
commit rejects, nothing is adopted, the store goes on. The store stops (`storage_failed`: later calls reject
`unavailable/storage_failed`, the host closes, `onError` receives `{ kind: "storage_failed" }`, the app decides,
section 3) only when the outcome is unknown (`COMMIT` or `ROLLBACK` failed) or `adopt` threw, and on a backend
error of a read whose `ctx` was not aborted. A Chord object whose commit the step refused (memory and disk disagree:
the counted conditional `UPDATE`, the live-address index) fails live (`object_failed`, `broken`); an `int` result out
of range (`CHECK`) fails only that transaction (`invalid/args`). Durable's Session fails on any commit error
(`GuardedStorage`), as today.
---

View file

@ -71,10 +71,8 @@ kinds and keys) keep Durable's JSON encoding in their columns.
orders, the fork chain walked as `SqliteStorage` does), `task`, `scanTasks`, `submission`, `scanSubmissions`,
`submissionByRequest`, `scanConversations`: queries over the declared tables, Durable's cursors (`{ after, order }`,
`scan.ts`) kept as they are (built from the last id, not from Chord's table cursors).
- **Validation before the step** (D4 below): what `SqliteStorage` checks inside its transaction
(`checkGlobalIds`, `prepareDocumentActions`, `checkDocumentActions`) runs before `store.commit`, with plain reads:
only Durable's Session writes these tables and it serializes its commits, so the reads see the state the step will
write on (the argument of S11).
- **Validation in the step** (D4): what `SqliteStorage` checks inside its transaction (`checkGlobalIds`,
`prepareDocumentActions`, `checkDocumentActions`) runs inside the store's step, which rolls back a refused commit.
### 1.2 IDs
@ -115,13 +113,13 @@ scope as a string (`session`, `conversation:7`, `task:31`), kind and key JSON-en
- **Durable's seq is the store's seq** (gaps allowed: Chord's commits take seqs too).
- **Errors:**
> **Decision needed (D4): what a refused Durable commit does to the store.** The store's rule: any backend error in a
> step is `storage_failed`, the store stops (Durable's own rule for its Session). Durable's conformance suite commits
> invalid batches on purpose ("ID 1 already belongs to conversation", "already has a current incarnation", "version
> transition requires a base", a mismatched copy) and expects the storage to go on. Proposal: those checks run before
> the step (1.1) and reject there, the store untouched; only a backend failure inside the step stops the store. In
> production the Session fails on any commit error either way (`GuardedStorage`), so the difference is only that the
> store, shared with Chord's objects, does not stop for a Durable bug caught by its checks.
**Errors (D4, owner, 2026-10-27):** the store takes Durable's `Storage` semantics (design §10 "Store errors"): a
step that fails before `COMMIT` rolls back and rejects that commit only; the store stops only on an unknown outcome
(`COMMIT` or `ROLLBACK` failed), a throwing `adopt`, or a read's backend error. Owners react as Durable's Session:
Durable's Session fails (`GuardedStorage`, unchanged); a Chord object whose commit the step refused fails live
(`object_failed`, `broken`); an `int` `CHECK` failure fails only its transaction (`invalid/args`). `StoreStorage` runs
Durable's commit checks in the step, as `SqliteStorage` does, so the conformance suite's refused commits leave the
store usable. 6b changes 6a's step and object failure accordingly.
- `mintId`, `close(context)` (closes nothing of the store: the app owns it; later calls reject "closed"), reads after
a store failure reject as Durable's `GuardedStorage` expects.