From b3f5cedff0f715d17329ecf924b170307f750b08 Mon Sep 17 00:00:00 2001 From: "Claude (dbsp-circuit)" Date: Sat, 10 Oct 2026 12:10:29 -0700 Subject: [PATCH] test(zset-ffi): the rebuild is one tick and its last chunk is Done (improvise-bw4p.1) Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze --- context/code/zset-ffi.md | 8 ++++++-- context/test-evidence/zset-ffi.md | 7 +++++++ crates/zset-ffi/tests/abi.rs | 33 +++++++++++++++++++++++++++++++ 3 files changed, 46 insertions(+), 2 deletions(-) diff --git a/context/code/zset-ffi.md b/context/code/zset-ffi.md index e0e56a8a..f3373e9d 100644 --- a/context/code/zset-ffi.md +++ b/context/code/zset-ffi.md @@ -33,8 +33,8 @@ link, so they call the `extern "C"` functions as plain functions. 4. **Failure leaves the Z-set unchanged.** `add`, `sub`, `neg`, `scale` and `decode_add` compute a new value and then assign it, so an overflow half way through cannot leave a half-applied update (`zs_zset_insert` is one addition, so it has no half). -5. **A failed step poisons the circuit.** A panic in `step` can leave operator state half - updated, and the circuit has no rollback (ADR 009 clause 11 is the host's). So the first +5. **A failed step poisons the circuit.** A panic in `step` leaves operator state half + updated, and the circuit has no rollback (ADR 009 clause 11 is the host's; `zset.md`). So the first failure in a step, or in a rebuild step, marks the circuit poisoned, and every later call that uses it returns `ZS_ERR_POISONED`. A caller drops it and builds another. Ids that do not exist, and malformed arguments, are refused before any state changes and do not poison. @@ -69,6 +69,10 @@ link, so they call the `extern "C"` functions as plain functions. 12. **Versioning.** `zs_abi_version()` and `ZS_ABI_VERSION` are 1. A change that breaks a signature or a code bumps it. +A rebuild is one logical tick however many `zs_rebuild_step` calls it takes (`zset.md`, +decision 8): a `delay` fed by it shows the previous tick once, and `done` is 1 as soon as the +last chunk ran, whatever the deadline callback says. + ## Calling it from Common Lisp `crates/zset-ffi/tests/smoke.lisp` is a runnable CFFI program (`defcfun` for each call, a diff --git a/context/test-evidence/zset-ffi.md b/context/test-evidence/zset-ffi.md index fcb5a9eb..e74b241f 100644 --- a/context/test-evidence/zset-ffi.md +++ b/context/test-evidence/zset-ffi.md @@ -58,3 +58,10 @@ counts; asserts the scanner saw more than 30 functions, so an empty parse cannot Every `ZS_ERR_*`, `ZS_ABI_VERSION`, `ZS_OK` and the four `ZS_AGG_*` equal the Rust values; the last code's number equals the number of codes, so a new code without a header line fails. + +### a_chunked_rebuild_through_delay_is_one_tick + +After #345's review the rebuild is one logical tick. A snapshot of two keys, chunks of one, +an always-expired deadline: the first call is `done == 0`, the second `done == 1` (the last +chunk is Done despite the deadline), and a `delay` sink shows the previous tick's key once. +Under the old per-chunk ticking the delay showed the first chunk instead. diff --git a/crates/zset-ffi/tests/abi.rs b/crates/zset-ffi/tests/abi.rs index 7c358819..730026be 100644 --- a/crates/zset-ffi/tests/abi.rs +++ b/crates/zset-ffi/tests/abi.rs @@ -497,3 +497,36 @@ fn an_index_by_callback_that_emits_nothing_is_a_bad_value() { assert_eq!(zs_circuit_step(c.0), Code::BadValue as i32); assert!(last_error().contains("exactly one key"), "{}", last_error()); } + +#[test] +fn a_chunked_rebuild_through_delay_is_one_tick() { + let snapshot = zset(&[(b"a", 1), (b"b", 1)]); + let c = C::new(); + let i = c.input(); + let mut d = 0; + assert_eq!(zs_circuit_delay(c.0, i, &mut d), OK); + let s = c.sink(d); + c.push(i, &[(b"p", 1)]); + assert_eq!(zs_circuit_step(c.0), OK); + c.take(s); + let mut job = null_mut(); + assert_eq!(zs_rebuild_new(c.0, i, snapshot, 1, &mut job), OK); + let (mut calls, mut done) = (0u32, 0); + let ctx = (&mut calls as *mut u32).cast(); + assert_eq!( + zs_rebuild_step(job, c.0, Some(out_of_time), ctx, &mut done), + OK + ); + assert_eq!(done, 0); + assert_eq!( + zs_rebuild_step(job, c.0, Some(out_of_time), ctx, &mut done), + OK + ); + assert_eq!( + done, 1, + "the last chunk is Done though the deadline expired" + ); + assert_eq!(c.take(s), one(&[(b"p", 1)])); + zs_rebuild_free(job); + zs_zset_free(snapshot); +}