From 010020d26c1c1fb553342bccab7978c4f2f2c5c6 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 16 Jun 2026 16:22:46 +0000 Subject: [PATCH] =?UTF-8?q?test(surreal=5Fcontainer):=20additive=20seam=20?= =?UTF-8?q?falsifier=20=E2=80=94=20SurrealMailboxView=20=E2=86=92=20Versio?= =?UTF-8?q?nScheduler=20=E2=86=92=20KanbanMove?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The scheduler→kanban→view path (version tick lowers to the next Rubicon move over a MailboxSoaView) existed only as doc-comment prose + one single-tick in-crate unit test against a hand-rolled FakeView. Nothing asserted the REAL SurrealMailboxView read-glove across the full Rubicon lifecycle. This adds that bench — additive, zero source change, and the first integration-test file in this crate. Five kill-condition-first tests (each pins the CONTRACT the doc-comment claims, not current behaviour — avoiding the stale-test trap): - full_rubicon_arc_lowers_to_legal_successors: every forward tick emits a DAG-sanctioned edge (can_transition_to); walks Planning→CognitiveWork→ Evaluation→Commit + Plan→Planning. - absorbing_columns_schedule_no_move: Commit/Prune yield None (lifecycle terminates). - libet_anchor_only_on_sigma_commit_crossing: -550_000µs stamped only on Planning→CognitiveWork; 0 elsewhere. - lowering_is_deterministic: same (view, version, exec) → same move. - exec_target_rides_onto_the_move: backend selector survives lowering. Scope: IN-direction seam only (contract-space; not the gated planner-emit half per #496 §9). Verified locally: 5/5 pass; fmt clean. --- .../surreal_container/tests/scheduler_seam.rs | 125 ++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 crates/surreal_container/tests/scheduler_seam.rs diff --git a/crates/surreal_container/tests/scheduler_seam.rs b/crates/surreal_container/tests/scheduler_seam.rs new file mode 100644 index 00000000..b1b2c208 --- /dev/null +++ b/crates/surreal_container/tests/scheduler_seam.rs @@ -0,0 +1,125 @@ +//! Seam falsifier: `SurrealMailboxView` → `VersionScheduler::on_version` → `KanbanMove`. +//! +//! ADDITIVE. Asserts the IN-direction wiring the module doc-comments *claim* +//! ("a version tick, fed to `VersionScheduler::on_version`, lowers to the next +//! legal Rubicon move over a `MailboxSoaView`") actually holds end-to-end — +//! driven by the REAL `SurrealMailboxView` read-glove, not a hand-rolled fake, +//! across the FULL Rubicon lifecycle (the in-crate unit test covers one tick). +//! +//! Each test states its kill-condition: the observation that would prove the +//! seam WRONG. None of these pin "current behaviour" — they pin the contract +//! the doc-comments assert. + +use lance_graph_contract::kanban::{ExecTarget, KanbanColumn}; +use lance_graph_contract::scheduler::{DatasetVersion, NextPhaseScheduler, VersionScheduler}; +use lance_graph_contract::soa_view::MailboxSoaView; +use surreal_container::view::SurrealMailboxView; + +/// One empty-column view at `phase` (the scheduler reads only `phase()` + +/// `mailbox_id()` + `current_cycle()`; row columns are irrelevant to lowering). +fn view_at(phase: KanbanColumn) -> SurrealMailboxView<'static> { + SurrealMailboxView::from_columns(7, 5, 13, phase, &[], &[], &[], &[]) +} + +/// KILL-CONDITION: if any forward tick lowers to a column the Rubicon DAG does +/// not sanction (`can_transition_to` == false), the scheduler is mis-wired. +/// Walks the whole canonical arc through the real view. +#[test] +fn full_rubicon_arc_lowers_to_legal_successors() { + let scheduler = NextPhaseScheduler; + // The canonical forward arc the doc-comment names. + let arc = [ + (KanbanColumn::Planning, KanbanColumn::CognitiveWork), + (KanbanColumn::CognitiveWork, KanbanColumn::Evaluation), + (KanbanColumn::Evaluation, KanbanColumn::Commit), + (KanbanColumn::Plan, KanbanColumn::Planning), // re-deliberate + ]; + for (i, (from, want_to)) in arc.iter().enumerate() { + let mv = scheduler + .on_version(&view_at(*from), DatasetVersion(i as u64 + 1), ExecTarget::Native) + .unwrap_or_else(|| panic!("{from:?} must schedule a forward move")); + assert_eq!(mv.from, *from, "move.from must echo the observed phase"); + assert_eq!(mv.to, *want_to, "{from:?} must lower to {want_to:?}"); + // The DAG itself must sanction the emitted edge — the falsifier. + assert!( + from.can_transition_to(mv.to), + "scheduler emitted an illegal Rubicon edge {from:?}->{:?}", + mv.to + ); + } +} + +/// KILL-CONDITION: an absorbing column (`Commit`/`Prune`) is cycle-end — it must +/// schedule NOTHING. If `on_version` returns `Some` here, the lifecycle would +/// never terminate (a mailbox past Commit would keep being advanced). +#[test] +fn absorbing_columns_schedule_no_move() { + let scheduler = NextPhaseScheduler; + for phase in [KanbanColumn::Commit, KanbanColumn::Prune] { + assert!(phase.is_absorbing(), "{phase:?} must be absorbing (precondition)"); + assert!( + scheduler + .on_version(&view_at(phase), DatasetVersion(99), ExecTarget::Native) + .is_none(), + "{phase:?} is absorbing — it must schedule no advance" + ); + } +} + +/// KILL-CONDITION: the Libet `-550_000 µs` readiness-potential anchor is stamped +/// ONLY on the Planning→CognitiveWork Σ-commit crossing. If any other tick +/// carries a non-zero offset (or that crossing carries zero), the Libet anchor +/// is mis-placed. +#[test] +fn libet_anchor_only_on_sigma_commit_crossing() { + let scheduler = NextPhaseScheduler; + + let crossing = scheduler + .on_version(&view_at(KanbanColumn::Planning), DatasetVersion(1), ExecTarget::Native) + .expect("Planning advances"); + assert_eq!(crossing.to, KanbanColumn::CognitiveWork); + assert_eq!( + crossing.libet_offset_us, -550_000, + "the Σ-commit crossing must carry the -550ms Libet anchor" + ); + + for from in [KanbanColumn::CognitiveWork, KanbanColumn::Evaluation, KanbanColumn::Plan] { + let mv = scheduler + .on_version(&view_at(from), DatasetVersion(2), ExecTarget::Native) + .expect("non-absorbing column advances"); + assert_eq!( + mv.libet_offset_us, 0, + "{from:?} is not the Σ-commit crossing — Libet offset must be 0" + ); + } +} + +/// KILL-CONDITION: the scheduler is a pure function of (view, version, exec). +/// Same inputs MUST yield the same move — the determinism the whole +/// version-tick → lifecycle mechanism stands on. A difference proves hidden +/// state leaked in. +#[test] +fn lowering_is_deterministic() { + let scheduler = NextPhaseScheduler; + let a = scheduler + .on_version(&view_at(KanbanColumn::CognitiveWork), DatasetVersion(7), ExecTarget::Jit) + .expect("advances"); + let b = scheduler + .on_version(&view_at(KanbanColumn::CognitiveWork), DatasetVersion(7), ExecTarget::Jit) + .expect("advances"); + assert_eq!(a, b, "same (view, version, exec) must lower to the same move"); +} + +/// KILL-CONDITION: the `exec` backend selector must ride through the lowering +/// onto the emitted move unchanged (it names where the precipitated plan runs). +/// If it is dropped or rewritten, the planner's execution-target choice is lost. +#[test] +fn exec_target_rides_onto_the_move() { + let scheduler = NextPhaseScheduler; + for exec in [ExecTarget::Native, ExecTarget::Jit, ExecTarget::SurrealQl, ExecTarget::Elixir] { + let mv = scheduler + .on_version(&view_at(KanbanColumn::Planning), DatasetVersion(3), exec) + .expect("advances"); + assert_eq!(mv.exec, exec, "exec target must survive the lowering"); + } +}