Skip to content

Commit c6357e2

Browse files
committed
feat(contract): D-MBX-9-IN VersionScheduler — IN-direction reactive seam
The dual of MailboxSoaOwner (E-SUBSTRATE-IS-THE-SCHEDULER): lowers a Lance versions() tick to the next legal KanbanMove. Zero-dep, read-only over the MailboxSoaView (propose-not-dispose, R1); composes only existing contract types (MailboxSoaView / KanbanColumn / KanbanMove / ExecTarget). - scheduler::{DatasetVersion(u64), VersionScheduler (trait), NextPhaseScheduler} - NextPhaseScheduler: Rubicon forward-arc; Libet -550us on Planning->CognitiveWork; None on absorbing (Commit/Prune). 6 tests. - 509 contract lib tests (+6); scheduler.rs clippy pedantic+nursery clean. First verifiable slice of the "wire all loose ends" agent wave (4 grounding agents mapped reactive seam / thinking+JIT / world-spine / hot-path+bindspace). Firewall kept: EW64+markov_soa is the particle->wave; old Vsa16kF32 singleton hunted, not re-materialized. CI-gated twin (LanceVersionScheduler over VersionedGraph::versions()) named not written (no protoc offline). Board: AGENT_LOG wave entry + LATEST_STATE inventory + STATUS_BOARD D-MBX-9-IN row. https://claude.ai/code/session_012SorR8UbtEvYmbX8cXftj7
1 parent b6e3cc6 commit c6357e2

5 files changed

Lines changed: 255 additions & 0 deletions

File tree

.claude/board/AGENT_LOG.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,21 @@
1+
## [Main thread / Opus] grounding wave (4 agents) → VersionScheduler slice (D-MBX-9-IN)
2+
3+
**Branch:** claude/jolly-cori-clnf9 (reset onto merged main `b6e3cc6` = #444+#445/lance7). **Spans:** the "wire all loose ends" agent wave — 4 read-only grounding agents → synthesis → first verifiable slice. **Firewall KEPT (user ratified):** EW64+markov_soa is the particle→wave; the old `Vsa16kF32` singleton is hunted, never re-materialized.
4+
5+
**Cargo:** `cargo test -p lance-graph-contract`**509 green** (+6 scheduler); `scheduler.rs` clippy-clean (pedantic+nursery). Core/world-spine slices stay CI-gated (no protoc offline).
6+
7+
**Grounding map (board-vs-code, HIGH confidence):**
8+
- *Reactive seam:* contract-traits-only; no concrete `MailboxSoaOwner` impl; `MailboxSoA<N>` lacks a `phase` column AND still carries the deprecated `cycle` carrier (retire together); OUT/IN halves real but unjoined (`VersionedGraph::versions()`, callcenter `LanceVersionWatcher`); planner `KanbanMove` emit = honest dead-store (`style_strategy.rs:148`).
9+
- *Thinking/JIT:* StyleStrategy L1-3 WIRED, L4 emit deferred (P3b/OQ-11.7); `ExecTarget` = inert tag (no router); JIT cache real, `JitEngine` adapter (D1.1b) Queued; head2head = `a2a_blackboard` has `support[4]`+`dissonance`, no executor.
10+
- *World-spine:* DeepNSM emits SPO English-by-construction (no mode switch — correct); aerial codebook/ontology WIRED standalone; markov_soa WIRED-unverified-offline, NOT code-connected to aerial; keyframe(radix)+delta(CLAM) = design-only (`radix_register`/`DeltaCard` 0 hits); #444 locality PASSED.
11+
- *Hot-path:* `WitnessTable<64>`/`WitnessEntry` shipped; EW64 = 0 code symbols; Hebbian spreader = design (OQ-11.1); A3 `witness_arc` MISSING. **Bindspace hunt: 0 singletons, 12 LEGIT ephemeral bundles, exactly 1 RETIRE (`FingerprintColumns::cycle`, 4 sites).**
12+
13+
**Shipped — D-MBX-9-IN:** `contract::scheduler::{DatasetVersion, VersionScheduler, NextPhaseScheduler}` (IN-direction dual of `MailboxSoaOwner`; Lance `versions()` tick → next legal `KanbanMove`; read-only, zero-dep, 6 tests).
14+
15+
**OQ slate raised:** OQ-EW64-LAYOUT, OQ-11.1 (plasticity radius/decay), OQ-11.2 (witness-arc W), OQ-MARKOV-AERIAL, OQ-FANOUT-FREEZE, OQ-HEAD2HEAD-CRIT. OQ-11.6 partly resolved by surreal #32. **Debt:** stale lance pins in board text (cited 4.0.0/6.0.0; now lance 7 via #445) — sweep owed.
16+
17+
---
18+
119
## [Main thread / Opus + W1/W2 wave] world-spine vision + probe wave + markov_soa SoC + EW64-as-AriGraph
220

321
**Branch:** claude/jolly-cori-clnf9-worldspine (local, 21 commits ahead of origin/main) | **Spans:** the agnostic-lazy-world-spine + delta-card integration map vision docs; the W1+W2 autoattended wave; the markov_soa SoC re-home; the EW64-as-AriGraph note; the locality probe RUN.

.claude/board/LATEST_STATE.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@
4242

4343
## Current Contract Inventory (lance-graph-contract)
4444

45+
> **2026-05-31 — ADDED (D-MBX-9-IN, VersionScheduler contract slice, on `b6e3cc6`/lance7)**: `lance_graph_contract::scheduler::{DatasetVersion(u64), VersionScheduler (trait), NextPhaseScheduler (reference impl)}`. The IN-direction dual of `MailboxSoaOwner` (`E-SUBSTRATE-IS-THE-SCHEDULER`): `on_version<V: MailboxSoaView>(&V, DatasetVersion, ExecTarget) -> Option<KanbanMove>` lowers a Lance `versions()` tick to the next legal Rubicon `KanbanMove`; `NextPhaseScheduler` is the forward-arc reference (Libet `-550ms` anchor on Planning→CognitiveWork, `None` on absorbing). Read-only over the view (**propose-not-dispose**, R1); composes only existing contract types; zero-dep. 509 contract lib tests (+6); clippy pedantic-clean. CI-gated twin = `LanceVersionScheduler` over `VersionedGraph::versions()` via callcenter `LanceVersionWatcher`. Closes D-MBX-9 IN-direction at the type level (OUT twin + core impl remain CI-gated).
46+
4547
> **2026-05-31 — MERGED (#441, D-CLS arc, merge `a77e119`)**: `lance_graph_contract::class_view::{FieldMask (u64 presence bitmask), ClassView (resolver trait), ClassProjection, RenderRow}` + `ClassView::render_rows` (off-bits-skipped). `ClassId = u16` (reuses `soa_view::class_id`). The class meta-DTO **flies ABOVE the agnostic SoA** — labels/shape/DOLCE resolve LATE from the OGIT cache, nothing semantic in the row (C2 presence≠semantics; N3 stable positions; out-of-range mask bits IGNORED not folded — Codex P2). Ontology side: `class_resolver::RegistryClassView` (impls `ClassView` over the live `OntologyRegistry`, DOLCE via `classify_odoo`) + `odoo_blueprint::class_signature::{StructuralSignature, OdooEntity::signature()/object_view() carrier methods, audit, shape_families, curated_entities, corpus_summary}` (deterministic FNV-1a structural-hash group-by, NOT Aerial-cluster). Zero-dep preserved; extends `ontology::{ObjectView,FieldRef,DisplayTemplate}`, reuses `class_id` (no new newtype). 497 contract + 240 ontology lib tests. D-CLS-{FM,RES,SIG,AUDIT,RENDER} all Shipped.
4648
>
4749
> **2026-05-30 — PR-in-flight addition** (D-MBX-A6 Phase 2 — Rubicon lifecycle + ExecTarget): `lance_graph_contract::kanban::{ExecTarget (Native/Jit/SurrealQl/Elixir), RubiconTransitionError}` + `KanbanColumn::{next_phases, can_transition_to}` (the Rubicon lifecycle DAG) + `KanbanMove.exec: ExecTarget` field + `MailboxSoaOwner::try_advance_phase()` (checked lifecycle enforcement → `Result<KanbanMove, RubiconTransitionError>`). Zero-dep; `KanbanMove` still ≤16 B; 489 contract lib tests (+4); downstream cargo-check clean. Lifecycle enforcement + planner exec-target are now contract-level invariants. Resolves the #437 deferred exec-target NOTE. Cross-ref D-MBX-A6 / #437.

.claude/board/STATUS_BOARD.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -561,6 +561,7 @@ Plan path: `.claude/plans/unified-soa-convergence-v1.md`. Handover `.claude/hand
561561
| D-MBX-7 | `lance-graph` containers ≡ `MailboxSoA` layout ≡ `ndarray::simd_soa.rs`-aligned (1.4–4.2× SIMD payoff; hard prereq for SurrealDB transparent view) | lance-graph + ndarray | 300 | HIGH | **Queued** | gates on D-MBX-A2 + D-MBX-10 + D-MBX-11 + PR-NDARRAY-MIRI-COMPLETE |
562562
| D-MBX-8 | Σ10 commit stamps **t = −550 ms** wall-clock (Libet anchor) in `SigmaTierRouter`; downstream ractor START fires | sigma-tier-router + shader-driver | 120 | MED | **Queued** | gates on D-MBX-A4 + D-MBX-A6 Phase 1 |
563563
| D-MBX-9 | Rubicon kanban view in `surrealkv`-on-lance (4 columns: Planning · Cognitive work · Evaluation · Commit·Plan·Prune); ractor lifecycle hooks = kanban moves | surreal_container + ractor | 250 | HIGH | **Queued** | gates on D-MBX-7 + D-MBX-8 + surreal_container BLOCKED(B/C/D) resolved (OQ-11.6) + D-PERSONA-5 |
564+
| D-MBX-9-IN | contract slice of D-MBX-9 IN-direction (`E-SUBSTRATE-IS-THE-SCHEDULER`): `scheduler::{DatasetVersion, VersionScheduler, NextPhaseScheduler}` — Lance `versions()` tick → next legal `KanbanMove`, zero-dep, read-only-over-view (propose-not-dispose) | lance-graph-contract | 130 | LOW | **Shipped (contract)** | 509 lib tests (+6); clippy pedantic-clean; CI-gated twin `D-MBX-9-IN-impl` (LanceVersionScheduler over `VersionedGraph::versions()`) named not written |
564565
| D-MBX-10 | SoA version byte at layout root (`MailboxSoAHeader`); refuse v(N>M) bytes on v(M) reader; field-isolation matrix tests on every column op (`I-LEGACY-API-FEATURE-GATED` discipline) | lance-graph-contract | 100 | HIGH | **Queued** | foundation — should land early in P2; gates on OQ-11.5 |
565566
| D-MBX-11 | Lance `=6.0.0 → =6.0.1` patch bump (5 Cargo.toml files identified) | workspace Cargo.toml | 10 | LOW | **Queued (mechanical)** | none — can land in parallel with par-tile prereq |
566567
| D-MBX-12 | 8-PR workspace-wide consumer alignment: 12.1 AriGraph · 12.2 Vsa16k audit · 12.4 lance-graph · 12.5 planner · 12.6 shader-driver · 12.7 callcenter · 12.8 ontology audit · 12.9 thinking-styles | per-crate | 800 | per-PR | **Queued (multi-PR)** | sequencing per OQ-11.8: 12.4 → 12.5 → 12.6 → 12.7 → 12.1 → 12.9 → 12.2 → 12.8 |

crates/lance-graph-contract/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ pub mod recipes;
8585
pub mod repository;
8686
pub mod savants;
8787
pub mod scenario;
88+
pub mod scheduler;
8889
pub mod sensorium;
8990
pub mod sigma_propagation;
9091
pub mod sla;
@@ -101,4 +102,5 @@ pub mod world_model;
101102
pub use class_view::{ClassId, ClassProjection, ClassView, FieldMask, RenderRow};
102103
pub use collapse_gate::{CollapseGateEmission, GateDecision, MailboxId, MergeMode};
103104
pub use kanban::{ExecTarget, KanbanColumn, KanbanMove, RubiconTransitionError};
105+
pub use scheduler::{DatasetVersion, NextPhaseScheduler, VersionScheduler};
104106
pub use soa_view::{MailboxSoaOwner, MailboxSoaView};
Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
//! # `scheduler` — the IN-direction reactive seam (`E-SUBSTRATE-IS-THE-SCHEDULER`).
2+
//!
3+
//! The dual of [`crate::soa_view::MailboxSoaOwner`]. The two directions of the
4+
//! Rubicon kanban over the ONE per-mailbox SoA:
5+
//!
6+
//! - **OUT** ([`MailboxSoaOwner::try_advance_phase`](crate::soa_view::MailboxSoaOwner::try_advance_phase)):
7+
//! the ractor owner advances a phase → that commit becomes a Lance dataset
8+
//! **version** → a [`KanbanMove`] (`E-VERSION-ARC-IS-THE-KANBAN`).
9+
//! - **IN** (this module): the reverse subscription — a substrate `LIVE`/scheduled
10+
//! event over `Dataset::versions()` is **lowered to the next legal**
11+
//! [`KanbanMove`], which the owner then applies via `try_advance_phase`.
12+
//!
13+
//! This collapses "build a transparent view" into "LIVE-subscribe + schedule" —
14+
//! the same shape as a CI/PR webhook firing the next job (D-MBX-9,
15+
//! `E-SUBSTRATE-IS-THE-SCHEDULER`).
16+
//!
17+
//! ## Why it lives in the zero-dep contract
18+
//! It composes **only** [`MailboxSoaView`] + [`KanbanColumn`] + [`KanbanMove`] +
19+
//! [`ExecTarget`] — no `lance`, no `surreal`, no async runtime. The CI-gated core
20+
//! impl (`D-MBX-9-IN`: a `LanceVersionScheduler` subscribing to
21+
//! `VersionedGraph::versions()` via the callcenter `LanceVersionWatcher`) lands in
22+
//! a buildable downstream crate; this trait is its airgap.
23+
//!
24+
//! ## Invariant — propose, don't dispose
25+
//! [`VersionScheduler::on_version`] takes `&V` (never `&mut`): the scheduler only
26+
//! **proposes** the next move; the [`MailboxSoaOwner`](crate::soa_view::MailboxSoaOwner)
27+
//! is the sole mutator (R1 "one SoA never transformed"; mirrors the
28+
//! `MailboxSoaView` / `MailboxSoaOwner` read/write split).
29+
30+
use crate::kanban::{ExecTarget, KanbanColumn, KanbanMove};
31+
use crate::soa_view::MailboxSoaView;
32+
33+
/// A monotonic Lance dataset version — the surreal Timeline tick, i.e. one entry
34+
/// of `Dataset::versions()`. The IN-direction event carrier.
35+
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
36+
pub struct DatasetVersion(pub u64);
37+
38+
/// Lower a substrate version event into the **next legal** kanban move for a
39+
/// mailbox view, or `None` when no advance is due (the mailbox is in an absorbing
40+
/// column, or the scheduler's policy filters this tick out).
41+
///
42+
/// The dual of [`crate::soa_view::MailboxSoaOwner`]: a `VersionScheduler` is what a
43+
/// `surreal_container` `LIVE` query (or the callcenter `LanceVersionWatcher`) calls
44+
/// per `versions()` tick to decide whether — and how — the ractor owner should
45+
/// advance the mailbox lifecycle.
46+
pub trait VersionScheduler {
47+
/// Decide the next move for `view` on observing dataset version `at`. `exec`
48+
/// selects the backend the precipitated move runs on
49+
/// ([`ExecTarget::Native`]/[`Jit`](ExecTarget::Jit)/[`SurrealQl`](ExecTarget::SurrealQl)/[`Elixir`](ExecTarget::Elixir)).
50+
/// Returns `None` to schedule no advance (e.g. `view.phase().is_absorbing()`).
51+
fn on_version<V: MailboxSoaView>(
52+
&self,
53+
view: &V,
54+
at: DatasetVersion,
55+
exec: ExecTarget,
56+
) -> Option<KanbanMove>;
57+
}
58+
59+
/// The canonical reference scheduler: on every version, advance the mailbox along
60+
/// the Rubicon **forward arc** — the first legal successor of its current column —
61+
/// or yield `None` when the column is absorbing (`Commit`/`Prune`).
62+
///
63+
/// The "forward arc" is [`KanbanColumn::next_phases`]`().first()`:
64+
/// `Planning → CognitiveWork`, `CognitiveWork → Evaluation`, `Evaluation → Commit`,
65+
/// `Plan → Planning` (re-deliberate), `Commit`/`Prune` → none. It stamps the Libet
66+
/// anchor (`-550_000 µs`) on the `Planning → CognitiveWork` Σ-commit crossing and
67+
/// `0` elsewhere — matching the `MailboxSoaOwner::advance_phase` convention.
68+
///
69+
/// This is the substrate-free reference: real schedulers may gate on the
70+
/// `DatasetVersion` delta, choose `Plan`/`Prune` over the forward arc, or batch
71+
/// ticks — they implement [`VersionScheduler`] with their own policy.
72+
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
73+
pub struct NextPhaseScheduler;
74+
75+
impl VersionScheduler for NextPhaseScheduler {
76+
fn on_version<V: MailboxSoaView>(
77+
&self,
78+
view: &V,
79+
_at: DatasetVersion,
80+
exec: ExecTarget,
81+
) -> Option<KanbanMove> {
82+
let from = view.phase();
83+
// `next_phases()` is empty exactly for the absorbing columns (Commit/Prune):
84+
// `?` short-circuits to `None`, i.e. "the cycle ended — schedule nothing".
85+
let to = *from.next_phases().first()?;
86+
let libet_offset_us =
87+
if from == KanbanColumn::Planning && to == KanbanColumn::CognitiveWork {
88+
-550_000
89+
} else {
90+
0
91+
};
92+
Some(KanbanMove {
93+
mailbox: view.mailbox_id(),
94+
from,
95+
to,
96+
// Structural witness position (R4): the monotonic cycle stamp stands in
97+
// for the chain index until the A3 `witness_arc` column lands.
98+
witness_chain_position: view.current_cycle(),
99+
libet_offset_us,
100+
exec,
101+
})
102+
}
103+
}
104+
105+
#[cfg(test)]
106+
mod tests {
107+
use super::*;
108+
use crate::collapse_gate::MailboxId;
109+
110+
/// Minimal `MailboxSoaView` with a settable phase — proves the scheduler
111+
/// lowers a version event to the right move without any consumer crate
112+
/// (same pattern as `soa_view::tests::FakeSoa`).
113+
struct FakeView {
114+
id: MailboxId,
115+
phase: KanbanColumn,
116+
cycle: u32,
117+
}
118+
impl MailboxSoaView for FakeView {
119+
fn mailbox_id(&self) -> MailboxId {
120+
self.id
121+
}
122+
fn n_rows(&self) -> usize {
123+
0
124+
}
125+
fn w_slot(&self) -> u8 {
126+
(self.id & 0x3F) as u8
127+
}
128+
fn current_cycle(&self) -> u32 {
129+
self.cycle
130+
}
131+
fn phase(&self) -> KanbanColumn {
132+
self.phase
133+
}
134+
fn energy(&self) -> &[f32] {
135+
&[]
136+
}
137+
fn edges_raw(&self) -> &[u64] {
138+
&[]
139+
}
140+
fn meta_raw(&self) -> &[u32] {
141+
&[]
142+
}
143+
fn entity_type(&self) -> &[u16] {
144+
&[]
145+
}
146+
}
147+
148+
fn view(phase: KanbanColumn) -> FakeView {
149+
FakeView { id: 42, phase, cycle: 9 }
150+
}
151+
152+
#[test]
153+
fn planning_schedules_cognitive_work_with_libet_anchor() {
154+
let m = NextPhaseScheduler
155+
.on_version(&view(KanbanColumn::Planning), DatasetVersion(1), ExecTarget::Native)
156+
.expect("Planning is not absorbing");
157+
assert_eq!(m.from, KanbanColumn::Planning);
158+
assert_eq!(m.to, KanbanColumn::CognitiveWork); // forward arc, not the Prune veto
159+
assert_eq!(m.libet_offset_us, -550_000); // the Σ-commit Rubicon crossing
160+
assert_eq!(m.mailbox, 42);
161+
assert_eq!(m.witness_chain_position, 9); // current_cycle stamp
162+
}
163+
164+
#[test]
165+
fn mid_cycle_advances_carry_no_libet_anchor() {
166+
let cw = NextPhaseScheduler
167+
.on_version(&view(KanbanColumn::CognitiveWork), DatasetVersion(2), ExecTarget::Native)
168+
.unwrap();
169+
assert_eq!(cw.to, KanbanColumn::Evaluation);
170+
assert_eq!(cw.libet_offset_us, 0);
171+
172+
let ev = NextPhaseScheduler
173+
.on_version(&view(KanbanColumn::Evaluation), DatasetVersion(3), ExecTarget::Native)
174+
.unwrap();
175+
assert_eq!(ev.to, KanbanColumn::Commit); // forward arc = calcify
176+
assert_eq!(ev.libet_offset_us, 0);
177+
}
178+
179+
#[test]
180+
fn plan_re_deliberates_back_to_planning() {
181+
let m = NextPhaseScheduler
182+
.on_version(&view(KanbanColumn::Plan), DatasetVersion(4), ExecTarget::Native)
183+
.unwrap();
184+
assert_eq!(m.from, KanbanColumn::Plan);
185+
assert_eq!(m.to, KanbanColumn::Planning); // re-enter carrying the witness
186+
}
187+
188+
#[test]
189+
fn absorbing_columns_schedule_nothing() {
190+
// Commit + Prune are absorbing: the cycle has ended, no move is due.
191+
assert!(NextPhaseScheduler
192+
.on_version(&view(KanbanColumn::Commit), DatasetVersion(5), ExecTarget::Native)
193+
.is_none());
194+
assert!(NextPhaseScheduler
195+
.on_version(&view(KanbanColumn::Prune), DatasetVersion(6), ExecTarget::Native)
196+
.is_none());
197+
}
198+
199+
#[test]
200+
fn exec_target_threads_through_to_the_move() {
201+
// The scheduler carries the backend selection onto the precipitated move
202+
// (the Native/Jit/SurrealQl/Elixir routing tag for the IN-direction).
203+
for exec in [ExecTarget::Native, ExecTarget::Jit, ExecTarget::SurrealQl, ExecTarget::Elixir] {
204+
let m = NextPhaseScheduler
205+
.on_version(&view(KanbanColumn::Planning), DatasetVersion(7), exec)
206+
.unwrap();
207+
assert_eq!(m.exec, exec);
208+
}
209+
}
210+
211+
#[test]
212+
fn scheduled_move_is_a_legal_rubicon_edge() {
213+
// Whatever the scheduler proposes MUST be a legal transition the owner's
214+
// `try_advance_phase` will accept (no illegal-edge proposals).
215+
for phase in [
216+
KanbanColumn::Planning,
217+
KanbanColumn::CognitiveWork,
218+
KanbanColumn::Evaluation,
219+
KanbanColumn::Plan,
220+
] {
221+
let m = NextPhaseScheduler
222+
.on_version(&view(phase), DatasetVersion(8), ExecTarget::Native)
223+
.unwrap();
224+
assert!(
225+
m.from.can_transition_to(m.to),
226+
"{:?} -> {:?} must be a legal Rubicon edge",
227+
m.from,
228+
m.to
229+
);
230+
}
231+
}
232+
}

0 commit comments

Comments
 (0)