mirror of
https://github.com/ModernRelay/omnigraph.git
synced 2026-07-12 03:12:11 +02:00
416 lines
20 KiB
Markdown
416 lines
20 KiB
Markdown
|
|
---
|
|||
|
|
type: spec
|
|||
|
|
title: "RFC-026 — MemWAL streaming ingest"
|
|||
|
|
description: Adopts Lance MemWAL as OmniGraph's strategic streaming-write architecture, with durable per-row acknowledgement, graph-atomic folds, epoch-fenced quiescence, and explicit fresh-read cuts on the RFC-022 unified write path.
|
|||
|
|
status: draft
|
|||
|
|
tags: [eng, rfc, streaming, ingest, wal, memwal, lance, omnigraph]
|
|||
|
|
timestamp: 2026-07-10
|
|||
|
|
owner:
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
# RFC-026 — MemWAL streaming ingest
|
|||
|
|
|
|||
|
|
**Status:** Draft / for team review
|
|||
|
|
**Date:** 2026-07-10
|
|||
|
|
**Depends on:** [RFC-022](rfc-022-unified-write-path.md)'s unified write and
|
|||
|
|
generic recovery-sidecar protocol, plus
|
|||
|
|
[RFC-023](rfc-023-key-conflict-fencing.md) for the initial keyed graph-stream
|
|||
|
|
mode. Durable heads from
|
|||
|
|
[RFC-024](rfc-024-durable-table-heads.md) are compatible but not required.
|
|||
|
|
**Surveyed:** omnigraph 0.8.1; Lance 9.0.0-beta.15 (`f24e42c1`); complete MemWAL format specification
|
|||
|
|
**Audience:** engine, server, CLI, policy, and operations maintainers
|
|||
|
|
**Open architecture review:** [RFC-022–027 review ledger](../dev/rfc-022-027-architecture-review.md).
|
|||
|
|
Findings marked **BLOCKER** must be dispositioned before acceptance.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 0. Decision and risk posture
|
|||
|
|
|
|||
|
|
OmniGraph adopts Lance MemWAL as its strategic streaming-write architecture.
|
|||
|
|
MemWAL is a major Lance architectural bet: a sharded LSM write path with durable
|
|||
|
|
WAL entries, flushed Lance generations, merge progress committed with base-table
|
|||
|
|
data, maintained indexes, and epoch-fenced writers. OmniGraph consumes that
|
|||
|
|
architecture rather than building a WAL, shard protocol, or LSM reader.
|
|||
|
|
|
|||
|
|
This RFC does **not** characterize the architecture as experimental. The risk is
|
|||
|
|
narrower: Rust API names, some format details, and operational helpers are still
|
|||
|
|
maturing across Lance releases. We manage that API/format-maturity risk with a
|
|||
|
|
small adapter, compile/runtime surface guards, a quiescence requirement before
|
|||
|
|
Lance upgrades, and a fresh full-spec alignment audit on every bump. It is not a
|
|||
|
|
reason to fork or reimplement MemWAL.
|
|||
|
|
|
|||
|
|
The contract is:
|
|||
|
|
|
|||
|
|
- stream acknowledgement means the row's WAL entry is durable;
|
|||
|
|
- acknowledgement does not mean graph visibility;
|
|||
|
|
- default queries see only the manifest-committed graph;
|
|||
|
|
- a fold is an ordinary RFC-022 graph writer and is the sole visibility point;
|
|||
|
|
- fresh reads are explicit and never claim cross-table atomicity.
|
|||
|
|
|
|||
|
|
## 1. Scope and non-goals
|
|||
|
|
|
|||
|
|
This RFC specifies enrollment, the public stream API, acknowledgement semantics,
|
|||
|
|
folding, fold-time integrity, dead-letter atomicity, branch/schema quiescence,
|
|||
|
|
fresh-read cuts, resource bounds, observability, testing, and upgrade posture.
|
|||
|
|
|
|||
|
|
It does not replace `load` or `mutate`, provide cross-query transactions, store
|
|||
|
|
manifest mutations in MemWAL, create a second metadata authority, or weaken
|
|||
|
|
default snapshot isolation. Stream-mode
|
|||
|
|
deletes remain out of the first delivery and require the Lance tombstone surface
|
|||
|
|
plus a separate acceptance pass.
|
|||
|
|
|
|||
|
|
## 2. Stream mode and key semantics
|
|||
|
|
|
|||
|
|
Initial delivery exposes
|
|||
|
|
`@stream(mode="upsert", on_reject="strict")` on a node or edge type;
|
|||
|
|
`on_reject` accepts `strict` or `dead_letter`.
|
|||
|
|
It requires the table's immutable unenforced primary key to equal OmniGraph's
|
|||
|
|
merge key: `id` for nodes and edges. All occurrences of one key map to one shard
|
|||
|
|
and MemWAL applies last-write-wins ordering.
|
|||
|
|
|
|||
|
|
Public append mode is deliberately out of scope. Nodes and edges always have
|
|||
|
|
logical identity; allowing a retry to append the same `id` twice would violate
|
|||
|
|
that contract. A future explicitly keyless, non-graph append-only table class
|
|||
|
|
may consume MemWAL append semantics under its own schema/API decision.
|
|||
|
|
|
|||
|
|
Stream ordering intentionally differs from the interactive fence:
|
|||
|
|
|
|||
|
|
- concurrent interactive same-key writes serialize or fail/retry loudly;
|
|||
|
|
- same-key stream entries resolve by MemWAL generation/position order;
|
|||
|
|
- duplicate keys inside one bulk-load input retain the existing load error.
|
|||
|
|
|
|||
|
|
The schema and user docs state all three together.
|
|||
|
|
|
|||
|
|
## 3. Enrollment is a recoverable inline commit
|
|||
|
|
|
|||
|
|
Schema apply records `@stream` intent only. First stream use enrolls the physical
|
|||
|
|
table by creating the singleton `__lance_mem_wal` system index and its sharding
|
|||
|
|
configuration.
|
|||
|
|
|
|||
|
|
Enrollment advances Lance HEAD inline. It therefore uses the RFC-022 generic
|
|||
|
|
recovery protocol, not an ad-hoc state machine:
|
|||
|
|
|
|||
|
|
1. run and await RFC-022's synchronous recovery barrier;
|
|||
|
|
2. authorize, pin the manifest/schema/table state, and prepare a complete
|
|||
|
|
`ReadSet` containing schema identity, table entry/head, PK metadata, stream
|
|||
|
|
intent/configuration, and lifecycle-row absence;
|
|||
|
|
3. acquire any global claims and then the `(table, branch)` write queue in
|
|||
|
|
RFC-022 order, then freshly revalidate the complete `ReadSet`; a mismatch restarts
|
|||
|
|
before any inline effect;
|
|||
|
|
4. verify RFC-023's already-installed PK; enrollment never performs a first-use
|
|||
|
|
PK migration; validate the sharding configuration;
|
|||
|
|
5. write a generic sidecar with writer kind `stream_enrollment` and pre-commit
|
|||
|
|
table pin;
|
|||
|
|
6. create the MemWAL index, advancing Lance HEAD;
|
|||
|
|
7. publish the new table version plus `stream_state = OPEN` in one manifest CAS,
|
|||
|
|
including the table-head row when RFC-024 is active;
|
|||
|
|
8. delete the sidecar best-effort after publication.
|
|||
|
|
|
|||
|
|
Recovery rolls the enrollment forward or back under the same classification and
|
|||
|
|
audit machinery as other inline residuals. Repeating enrollment with identical
|
|||
|
|
metadata is a no-op. A different PK, sharding spec, maintained-index set, or
|
|||
|
|
writer-default configuration is a typed conflict.
|
|||
|
|
|
|||
|
|
No row is acknowledged until enrollment is manifest-committed.
|
|||
|
|
|
|||
|
|
Initial delivery supports one unsharded shard per `(table, main)`. Non-main
|
|||
|
|
branches remain refused until the Lance branch-scoping question is proven by a
|
|||
|
|
surface guard and end-to-end test. Later `bucket(id, N)` sharding must preserve
|
|||
|
|
the one-key-to-one-shard rule.
|
|||
|
|
|
|||
|
|
## 4. New public API
|
|||
|
|
|
|||
|
|
The shipped `POST /graphs/{id}/ingest` path remains the deprecated, compatible
|
|||
|
|
alias of `/load`. Streaming receives a new, non-conflicting surface:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
POST /graphs/{graph_id}/streams/{type_name}/ingest?branch=main
|
|||
|
|
GET /graphs/{graph_id}/streams
|
|||
|
|
GET /graphs/{graph_id}/streams/{type_name}
|
|||
|
|
POST /graphs/{graph_id}/streams/{type_name}/fold
|
|||
|
|
POST /graphs/{graph_id}/streams/{type_name}/quiesce
|
|||
|
|
POST /graphs/{graph_id}/streams/{type_name}/resume
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
The ingest request and response use `Content-Type: application/x-ndjson` and
|
|||
|
|
`Accept: application/x-ndjson`.
|
|||
|
|
|
|||
|
|
Each input line is one row payload. Each output line corresponds to the same
|
|||
|
|
input ordinal:
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{"ordinal":17,"status":"durable","shard_id":"...","writer_epoch":8,"wal_position":42}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Synchronous validation failures return a per-row error before a WAL append.
|
|||
|
|
Previously acknowledged rows in the same request remain durable; the response
|
|||
|
|
is a stream, not an all-request transaction. Ordering, cancellation, and retry
|
|||
|
|
rules are explicit:
|
|||
|
|
|
|||
|
|
- acknowledgements are emitted in input order for one HTTP stream;
|
|||
|
|
- disconnecting does not cancel entries whose durability waiter resolved;
|
|||
|
|
- a missing response is ambiguous; retrying the same `id` and payload may add
|
|||
|
|
another WAL entry but produces the same last-write-wins graph state;
|
|||
|
|
- server shutdown stops admission, drains durability waiters up to a bound, and
|
|||
|
|
reports any unacknowledged tail as unknown to the client.
|
|||
|
|
|
|||
|
|
CLI commands mirror the new namespace rather than overloading deprecated
|
|||
|
|
`omnigraph ingest`:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
omnigraph stream ingest <type> --data <ndjson> ...
|
|||
|
|
omnigraph stream status [<type>] ...
|
|||
|
|
omnigraph stream fold [<type>] ...
|
|||
|
|
omnigraph stream quiesce [<type>] ...
|
|||
|
|
omnigraph stream resume [<type>] ...
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Every endpoint has a dedicated OpenAPI operation and handler tests. Ingest
|
|||
|
|
passes the engine `stream_ingest` Cedar action and per-actor admission
|
|||
|
|
accounting before acquiring a shard writer; fold/quiesce/resume use a separate
|
|||
|
|
`stream_manage` action. Status is authorized like other graph operational
|
|||
|
|
metadata. The same engine gates apply to embedded and remote CLI use.
|
|||
|
|
|
|||
|
|
## 5. Ack-path validation and writer lifecycle
|
|||
|
|
|
|||
|
|
Before append, OmniGraph applies checks that need no base-table read: Arrow
|
|||
|
|
shape/type, required/default fields, enum/range/check constraints, reserved
|
|||
|
|
columns, and stream mode. RI, cardinality, cross-version uniqueness, and
|
|||
|
|
external embedding computation remain fold-time work.
|
|||
|
|
|
|||
|
|
One warm `ShardWriter` is held per active shard behind a bounded registry. The
|
|||
|
|
registry has idle eviction and hard limits for resident writers, MemTable bytes,
|
|||
|
|
unflushed WAL bytes, pending generations, and per-actor inflight bytes. Exceeding
|
|||
|
|
a bound backpressures with a typed retryable response; it never drops a row.
|
|||
|
|
|
|||
|
|
Initial topology has one active ingest owner for each `(graph, table, main)`
|
|||
|
|
shard. MemWAL's epoch fence makes restart/failover safe; it is not a load
|
|||
|
|
balancer. A deployment with multiple server replicas must route a shard to its
|
|||
|
|
current owner (or return a typed retry/redirect) instead of letting replicas
|
|||
|
|
reclaim the epoch per request. General multi-owner routing waits for the
|
|||
|
|
multi-shard phase and its ownership protocol.
|
|||
|
|
|
|||
|
|
## 6. Fold protocol
|
|||
|
|
|
|||
|
|
The fold consumes flushed generations in ascending order. Embeddings and
|
|||
|
|
base-dependent validation run outside the table queue and register every probed
|
|||
|
|
table plus stream configuration/generation in RFC-022's `ReadSet`. The commit
|
|||
|
|
phase then:
|
|||
|
|
|
|||
|
|
1. stages accepted rows with Lance merge-insert and includes
|
|||
|
|
`merged_generations` in that transaction;
|
|||
|
|
2. stages any rejection/audit rows required by §7;
|
|||
|
|
3. acquires every affected queue in canonical sorted order and revalidates the
|
|||
|
|
complete `ReadSet`; any mismatch discards and replans the whole fold;
|
|||
|
|
4. writes one generic RFC-022 recovery sidecar before the first
|
|||
|
|
`commit_staged` call;
|
|||
|
|
5. commits every staged Lance transaction;
|
|||
|
|
6. publishes all data/internal table versions and lineage in one `__manifest`
|
|||
|
|
CAS, including table heads when RFC-024 is active;
|
|||
|
|
7. deletes the sidecar after successful publication.
|
|||
|
|
|
|||
|
|
The sidecar is mandatory even though merge-insert is staged. After
|
|||
|
|
`commit_staged`, Lance HEAD and `merged_generations` have moved while the graph
|
|||
|
|
manifest has not. A failure in that window is the ordinary multi-table recovery
|
|||
|
|
gap, not invisible staged state.
|
|||
|
|
|
|||
|
|
MemWAL generation GC starts only after the exact fold is graph-visible, its
|
|||
|
|
sidecar is resolved, index catchup permits reclamation, and no `FreshReadCut`
|
|||
|
|
retention guard references the generation. Data-HEAD merge progress alone is
|
|||
|
|
never permission to delete the only fresh-tier copy.
|
|||
|
|
|
|||
|
|
Concurrent folders reload `merged_generations`: a generation already committed
|
|||
|
|
is skipped; otherwise the fold is replanned from current state. A forced fold
|
|||
|
|
stops new flush creation for its cut, waits for in-flight durability waiters,
|
|||
|
|
and never aborts an acknowledged row.
|
|||
|
|
|
|||
|
|
Fold lineage uses `omnigraph:ingest` as the mechanism actor and persists the
|
|||
|
|
authenticated contributor actor for every folded WAL range in the same internal
|
|||
|
|
audit participant. Commit and status output can therefore answer both who ran
|
|||
|
|
the fold and who supplied the data after WAL GC.
|
|||
|
|
|
|||
|
|
## 7. Fold-time rejection is atomic
|
|||
|
|
|
|||
|
|
`strict` is the default. A permanent RI, cardinality, uniqueness, or embedding
|
|||
|
|
failure stops the fold at the offending generation, marks the shard blocked,
|
|||
|
|
and backpressures new ingestion once configured lag bounds are reached.
|
|||
|
|
|
|||
|
|
`dead_letter` is explicit. `_ingest_rejects` is then a versioned internal Lance
|
|||
|
|
table and a participant in the same fold pipeline, not best-effort state outside
|
|||
|
|
the commit protocol. Every reject has deterministic identity:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
(table_key, shard_id, generation, wal_position)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
The fold stages reject rows, accepted rows, and merge progress before committing
|
|||
|
|
any of them. The generic sidecar covers every participant; the single manifest
|
|||
|
|
publish records both the base-table and reject-table versions. Replay is
|
|||
|
|
idempotent by reject identity. There is no ordering in which progress can become
|
|||
|
|
visible while the corresponding rejection is lost.
|
|||
|
|
|
|||
|
|
`stream status` reports blocked generations and typed reject details. Reject
|
|||
|
|
retention is explicit and cannot be shorter than the WAL/fold audit retention
|
|||
|
|
needed to explain a durable acknowledgement.
|
|||
|
|
|
|||
|
|
## 8. Epoch-fenced quiescence barrier
|
|||
|
|
|
|||
|
|
Branch operations, schema changes, stream teardown, and Lance upgrades require a
|
|||
|
|
real barrier, not an empty check.
|
|||
|
|
|
|||
|
|
Each enrolled table has a durable
|
|||
|
|
`stream_state:<stable-table-id>:<incarnation>` row in its manifest branch with
|
|||
|
|
`OPEN | DRAINING | SEALED`, configuration hash, and epoch floor. The row is the
|
|||
|
|
logical lifecycle authority and is updated by an RFC-022 CAS; MemWAL shard
|
|||
|
|
epochs are the physical writer fence. Neither an in-memory registry nor an
|
|||
|
|
empty-generation observation can substitute for both.
|
|||
|
|
Lifecycle-only transitions are audited manifest metadata transactions; they do
|
|||
|
|
not create graph-content commits or move `graph_head`.
|
|||
|
|
|
|||
|
|
The shared drain sequence is:
|
|||
|
|
|
|||
|
|
1. publish stream intent `OPEN -> DRAINING` for the target table/branch;
|
|||
|
|
2. increment and persist each shard writer epoch and seal claims, fencing stale
|
|||
|
|
writers across processes;
|
|||
|
|
3. reject or backpressure new appends;
|
|||
|
|
4. wait for in-flight durability waiters, flush active MemTables, and fold every
|
|||
|
|
generation to empty;
|
|||
|
|
5. verify shard manifests and base `merged_generations` agree on emptiness;
|
|||
|
|
6. publish `DRAINING -> SEALED` with the verified generation/epoch cut;
|
|||
|
|
7. for an operation-scoped drain, perform the guarded operation; persistent
|
|||
|
|
public quiesce stops after step 6.
|
|||
|
|
|
|||
|
|
Each lifecycle CAS is an RFC-022 authority-first metadata write; each fold is a
|
|||
|
|
separate normal RFC-022 graph write. `DRAINING` fully encodes the target epoch
|
|||
|
|
floor, so an interrupted epoch mutation is idempotently resumed from that row.
|
|||
|
|
The sequence is not one giant sidecar spanning multiple commits.
|
|||
|
|
|
|||
|
|
There are two dispositions after the drain reaches `SEALED`:
|
|||
|
|
|
|||
|
|
- **operation-scoped drain** — branch/schema maintenance automatically publishes
|
|||
|
|
`SEALED -> OPEN` with a newer epoch only after the guarded operation succeeds
|
|||
|
|
and the stream contract remains compatible;
|
|||
|
|
- **persistent quiesce** — the public `quiesce` command leaves the stream
|
|||
|
|
`SEALED`. It never auto-reopens. `stream resume` explicitly revalidates schema,
|
|||
|
|
PK, configuration, MemWAL format, and epoch, then publishes a newer `OPEN`
|
|||
|
|
state. Stream teardown deletes intent only from `SEALED`.
|
|||
|
|
|
|||
|
|
The barrier never holds the table write queue while waiting for a fold that
|
|||
|
|
needs that queue. State transition and epoch fencing happen first; fold commit
|
|||
|
|
then acquires the normal queue. Crash recovery resumes from the durable state
|
|||
|
|
and epoch.
|
|||
|
|
|
|||
|
|
Schema apply must drain every affected enrolled type before changing fields,
|
|||
|
|
constraints, PK, embeddings, or `@stream` and resumes only when compatible.
|
|||
|
|
|
|||
|
|
A Lance version upgrade requires persistent `stream quiesce --all`, but empty
|
|||
|
|
generations alone are insufficient: the MemWAL system index, shard manifests,
|
|||
|
|
epoch records, and generation directories may still use the old format. Before
|
|||
|
|
the bump, the implementation must prove one of: (a) upstream guarantees and
|
|||
|
|
cross-version tests cover every retained MemWAL artifact, (b) a public Lance
|
|||
|
|
metadata migration converts them, or (c) OmniGraph tears down the enrolled
|
|||
|
|
MemWAL metadata under recovery and re-enrolls after the bump. Without one of
|
|||
|
|
those gates the upgrade refuses; `resume` never opens unverified old metadata.
|
|||
|
|
|
|||
|
|
## 9. Fresh-read cuts
|
|||
|
|
|
|||
|
|
Freshness is a first-class engine/IR enum:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
Committed
|
|||
|
|
Fresh
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
At query planning, `Fresh` captures one `FreshReadCut` containing:
|
|||
|
|
|
|||
|
|
- the ordinary manifest snapshot;
|
|||
|
|
- each selected shard-manifest version and writer epoch;
|
|||
|
|
- included flushed-generation paths and maximum generation;
|
|||
|
|
- the active same-process MemTable row-position watermark, when available;
|
|||
|
|
- the base table's `merged_generations` and index-catchup state read from the
|
|||
|
|
exact table version selected by the manifest snapshot, never from live HEAD.
|
|||
|
|
|
|||
|
|
Capture uses a retrying handshake:
|
|||
|
|
|
|||
|
|
1. read the selected shard manifests/epochs, acquire Lance generation retention
|
|||
|
|
guards for the flushed files in the tentative cut, and under one
|
|||
|
|
same-process writer snapshot capture/pin any active-MemTable watermark;
|
|||
|
|
2. pin the graph manifest snapshot and read `merged_generations` from each exact
|
|||
|
|
base-table version it selects;
|
|||
|
|
3. re-read the shard manifest versions/epochs; any epoch/configuration change
|
|||
|
|
restarts the whole capture;
|
|||
|
|
4. if a generation from step 1 disappeared, accept that only when the pinned
|
|||
|
|
base's `merged_generations` proves it is included; otherwise release guards,
|
|||
|
|
discard the whole graph snapshot, and retry from step 1;
|
|||
|
|
5. exclude generations that appeared after step 1 and hold the generation and
|
|||
|
|
MemTable read guards captured in step 1 until query completion.
|
|||
|
|
|
|||
|
|
If Lance exposes no guard that prevents generation GC for the query lifetime,
|
|||
|
|
cross-process `Fresh` does not ship. A missing generation is never interpreted
|
|||
|
|
as “probably folded” against an older pinned base.
|
|||
|
|
|
|||
|
|
Execution never refreshes that cut mid-query. It excludes every flushed
|
|||
|
|
generation `<= merged_generations[shard]`; otherwise old WAL data could outrank
|
|||
|
|
or duplicate its newer base-table image.
|
|||
|
|
|
|||
|
|
Fresh reads have no cross-table atomicity. Same-process active MemTables provide
|
|||
|
|
read-your-writes; other processes can promise only the latest flushed state
|
|||
|
|
captured by their shard-manifest reads. The HTTP request and query docs state
|
|||
|
|
those limits wherever the tier is exposed.
|
|||
|
|
|
|||
|
|
## 10. Observability and resource contracts
|
|||
|
|
|
|||
|
|
Per shard expose durable WAL position, replay position, active epoch, current
|
|||
|
|
generation, flushed and merged generation, index catchup, pending rows/bytes,
|
|||
|
|
oldest acknowledged age, last fold error, blocked reject, and quiescence state.
|
|||
|
|
|
|||
|
|
Metrics cover ack latency, durability-wait batching, fenced writers, replayed
|
|||
|
|
entries, fold rows/bytes/generations, fold retries, lag, reject counts, and
|
|||
|
|
sidecar recovery. Defaults for every byte/count/time bound are documented and
|
|||
|
|
configuration changes are observable behavior.
|
|||
|
|
|
|||
|
|
`stream status` resolves the exact lifecycle rows and MemWAL metadata through a
|
|||
|
|
structured, bounded access path; it may reuse RFC-024's scalar-index machinery
|
|||
|
|
but cannot claim history-flat cost while scanning manifest history.
|
|||
|
|
|
|||
|
|
## 11. Acceptance gates
|
|||
|
|
|
|||
|
|
- Surface guards pin claim, append, durability waiter, flush, epoch fencing,
|
|||
|
|
staged merge with `merged_generations`, index catchup, and seal/reopen APIs.
|
|||
|
|
- A WAL append failure emits no durable acknowledgement. Every acknowledged row
|
|||
|
|
survives crash, replay, fold, and recovery.
|
|||
|
|
- Failpoints cover enrollment's inline-commit gap and every fold participant
|
|||
|
|
around sidecar, `commit_staged`, reject persistence, and manifest publish.
|
|||
|
|
- Enrollment restarts without an inline effect when schema, PK, table head, or
|
|||
|
|
stream configuration changes between prepare and gated revalidation.
|
|||
|
|
- Two folders converge exactly once; a fenced stale writer can never produce a
|
|||
|
|
false durable acknowledgement after WAL GC.
|
|||
|
|
- Two server replicas do not epoch-ping-pong one shard; owner failover fences
|
|||
|
|
the old process before the new owner acknowledges.
|
|||
|
|
- Quiescence tests race appends with branch and schema operations across two
|
|||
|
|
coordinators and prove no post-drain tail appears; failpoints cover every
|
|||
|
|
lifecycle-row, epoch-fence, fold, and `SEALED` boundary.
|
|||
|
|
- Persistent quiesce never auto-reopens; explicit resume validates a newer
|
|||
|
|
epoch. Upgrade tests cover every retained MemWAL artifact through declared
|
|||
|
|
compatibility, migration, or teardown/re-enrollment.
|
|||
|
|
- Fresh reads race capture with fold/GC, retry on an unexplained disappearing
|
|||
|
|
generation, hold generation/MemTable guards through execution, exclude merged
|
|||
|
|
generations, and document cross-table inconsistency explicitly.
|
|||
|
|
- Server/OpenAPI tests preserve the old `/ingest` alias and cover the new route;
|
|||
|
|
CLI parity covers embedded and remote stream commands.
|
|||
|
|
- Ack-path object-store operations are O(1) and flat in graph history and WAL
|
|||
|
|
depth. Fold cost is bounded by generations/rows folded, not graph history.
|
|||
|
|
- S3 correctness runs against RustFS; API/format guards are rerun before every
|
|||
|
|
Lance bump.
|
|||
|
|
|
|||
|
|
## 12. Phasing
|
|||
|
|
|
|||
|
|
| Phase | Content | Gate |
|
|||
|
|
|---|---|---|
|
|||
|
|
| A | MemWAL adapter, surface guards, enrollment sidecar | inline-commit crash matrix |
|
|||
|
|
| B | new ingest route/CLI, durable ack, strict fold | ack durability; API compatibility; cost budget |
|
|||
|
|
| C | atomic dead letter, audit provenance, status/bounds | reject crash matrix; backpressure tests |
|
|||
|
|
| D | epoch-fenced drain, persistent quiesce/resume, schema/branch/upgrade integration | two-coordinator race and format-transition suite |
|
|||
|
|
| E | fresh cuts and maintained-index reads; cross-process `Fresh` ships only if the substrate generation-retention guard exists (§9), otherwise same-process only | cut consistency; merged-generation exclusion |
|
|||
|
|
| F | multi-shard upsert and stream deletes | one-key-one-shard proof; Lance re-audit |
|