Skip to content

Commit 55fc45c

Browse files
committed
fix(kvs-lance): fail fast on a pre-seq legacy dataset (codex P2 on #30)
`max_persisted_seq` treated a dataset whose schema lacks the `seq` column as success (returning 0), but the write paths now always build 5-column batches, so the first flush/merge against such a 4-column dataset would hit an opaque Lance schema mismatch. Return a clear migration error instead — exactly the "fail with a clear migration error before allowing commits" Codex suggested. A fresh dataset created by this code always carries the 5-column schema, so this only fires for genuinely legacy (pre-release) data; all 4 seq tests pass. Also records the Cognitive-RISC substrate↔invariant mapping (and the N1 trap: do not add class_id to the kv-lance schema — it stays policy-free) in .claude/board/EPIPHANIES.md. https://claude.ai/code/session_01R9AWgFa65uPnLyS2my2d2R
1 parent e329a7a commit 55fc45c

2 files changed

Lines changed: 42 additions & 2 deletions

File tree

.claude/board/EPIPHANIES.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,3 +171,36 @@ Documented (accepted, pre-release) limitations the savants surfaced:
171171
disagree with commit order (harmless today; reads never consult seq).
172172
173173
**Cross-ref:** `.claude/board/GRIDLAKE_REVIEW.md` (S1/S2/S3); fix in this commit.
174+
175+
## 2026-05-30 — kv-lance substrate maps onto Cognitive-RISC invariants; do NOT add class_id to it
176+
**Status:** FINDING
177+
**Scope:** `kvs/lance/*` vs `lance-graph/.claude/specs/{cognitive-risc-core,cognitive-risc-classes,wikidata-hhtl-load,faiss-homology-cam-pq}.md`
178+
179+
The kv-lance backend IS the "Substrate" layer (row 1) of the Cognitive-RISC
180+
five-layer stack ("SoA, LE byte contract, surrealkv WAL/ACID, policy-free
181+
state"). Concrete mapping: CommitGate/single_lance_commit = the sole cold-path
182+
writer (invariant #4); WAL+memtable ↔ flusher→Lance two-clock decoupling +
183+
the adaptive-batching rate-floor = the shock absorber (#7); WAL carries KV
184+
rows only, never compiled candidates (#11); the schema is opaque (key,val) +
185+
MVCC bookkeeping version/tombstone/seq with ZERO domain meaning (#1, and #6
186+
permits generation/tombstone counters). The step-2 `seq_survives_restart`
187+
test is exactly the spec's "smallest first slice" (WAL round-trip + read back
188+
after a simulated restart).
189+
190+
TRAP recorded so a future session does not weld the inversion shut: freeze-
191+
time move **N1 ("add class_id/shape_id to the SoA")** must NOT be applied to
192+
the kv-lance schema — that violates invariant #1. class_id, HHTL nibble-path,
193+
facet bitmasks, and the CAM (BLAKE) hash live ONE LAYER UP (inside the `val`
194+
payload or lance-graph's own Lance datasets), never as kv-lance columns. The
195+
minimal key/val/version/tombstone/seq schema is correct precisely because it
196+
is policy-free.
197+
198+
Live fork for this work — **F2**: spec default-leans "federate via DataFusion
199+
catalog (Arrow TableProviders)", not "read Lance directly (heavy/fragile)".
200+
kv-lance is the direct path; the step-1 Timeline ("SurrealDB-as-view-over-
201+
Lance", Rubicon) is the federation-shaped read surface. Decide: SurrealDB as
202+
writer-of-record into Lance (kv-lance) vs DataFusion-federated view (F2); they
203+
can coexist but the version-coupling risk is real. Version pin skew: repo on
204+
lance =6.0.0/arrow 58; spec pins lance 6.0.1/lancedb 0.29/datafusion 53.
205+
206+
**Cross-ref:** PR #29/#30; lance-graph .claude/specs/ (sha d1635db).

surrealdb/core/src/kvs/lance/mod.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -203,9 +203,16 @@ impl Datastore {
203203
async fn max_persisted_seq(ds: &LanceDataset) -> Result<u64> {
204204
use futures::TryStreamExt;
205205
let mut scanner = ds.scan();
206-
// Tolerate a legacy dataset with no `seq` column.
206+
// A dataset whose schema lacks `seq` predates this column (a pre-release
207+
// on-disk format change). Fail fast with a clear migration error rather
208+
// than letting the first 5-column merge hit an opaque schema mismatch
209+
// (codex P2 on #30). A fresh dataset created by this code always carries
210+
// the 5-column schema, so this only fires for genuinely legacy data.
207211
if scanner.project(&["seq"]).is_err() {
208-
return Ok(0);
212+
return Err(Error::Datastore(
213+
"Lance dataset predates the `seq` column (pre-release on-disk format change); a backfill/migration is required before writes (see .claude/board/EPIPHANIES.md, 2026-05-30 seq column)."
214+
.to_string(),
215+
));
209216
}
210217
let mut stream = scanner
211218
.try_into_stream()

0 commit comments

Comments
 (0)