Skip to content

Commit fd1d58f

Browse files
committed
fix(supervisor): preserve scheduler exec target in drive_scheduled_tick (codex #579)
The scheduler stamps the chosen backend onto proposed.exec, but the consumer sent only proposed.to and returned the owner's move — and owners default to ExecTarget::Native, so a Jit/SurrealQl/Elixir selection was silently reported and routed as Native. Fix: keep the owner's emitted move (authoritative phase transition, witness position, libet anchor from the real mutation) but overlay proposed.exec — the backend routing tag is the policy's decision, which the owner can't make. +1 test (13 total green): scheduled_tick_preserves_non_native_exec_target asserts Jit/SurrealQl/Elixir survive round-trip. clippy + fmt clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CcpLeEC3XK8Eye53GKBVvi
1 parent 2f5ed3d commit fd1d58f

1 file changed

Lines changed: 55 additions & 7 deletions

File tree

crates/lance-graph-supervisor/src/kanban_actor.rs

Lines changed: 55 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -303,9 +303,12 @@ pub async fn drive_version_tick(
303303
/// message (from the supplied `view`), so it is **advisory**: if the owner's phase
304304
/// changes between the proposal and the `Advance`, the edge may be rejected
305305
/// ([`KanbanRouteError::Illegal`]) rather than silently corrupting — surfaced, not
306-
/// panicked. For the pure forward-arc policy prefer the atomic
307-
/// [`drive_version_tick`]; reach for this only when the policy needs a richer view
308-
/// than the owner computes internally.
306+
/// panicked. The returned move is the owner's (authoritative phase transition,
307+
/// witness position, and libet anchor from the REAL mutation) with the
308+
/// **scheduler's `exec` overlaid** — the backend routing tag is the policy's
309+
/// decision, which the owner (defaulting to `Native`) can't make. For the pure
310+
/// forward-arc policy prefer the atomic [`drive_version_tick`]; reach for this
311+
/// only when the policy needs a richer view than the owner computes internally.
309312
pub async fn drive_scheduled_tick<S, V>(
310313
scheduler: &S,
311314
view: &V,
@@ -327,10 +330,21 @@ where
327330
reply
328331
})
329332
.map_err(|e| KanbanRouteError::Rpc(e.to_string()))?;
330-
inner.map(Some).map_err(|e| KanbanRouteError::Illegal {
331-
from: e.from,
332-
to: e.to,
333-
})
333+
match inner {
334+
// The owner's emitted move is authoritative for the phase transition,
335+
// witness position, and libet anchor (from the REAL mutation), but it
336+
// defaults to `ExecTarget::Native` and can't know which backend the policy
337+
// chose — overlay the scheduler's selection so a `Jit`/`SurrealQl`/`Elixir`
338+
// target is not silently reported/routed as Native (codex #579 P2).
339+
Ok(mut emitted) => {
340+
emitted.exec = proposed.exec;
341+
Ok(Some(emitted))
342+
}
343+
Err(e) => Err(KanbanRouteError::Illegal {
344+
from: e.from,
345+
to: e.to,
346+
}),
347+
}
334348
}
335349

336350
#[cfg(test)]
@@ -689,4 +703,38 @@ mod tests {
689703
actor.stop(None);
690704
handle.await.expect("actor join");
691705
}
706+
707+
#[tokio::test]
708+
async fn scheduled_tick_preserves_non_native_exec_target() {
709+
use lance_graph_contract::scheduler::NextPhaseScheduler;
710+
711+
// codex #579 P2: the scheduler selects the backend; the owner defaults to
712+
// `Native`. The returned move must carry the scheduler's exec, NOT be
713+
// flattened to the owner's Native default.
714+
for exec in [ExecTarget::Jit, ExecTarget::SurrealQl, ExecTarget::Elixir] {
715+
// Fresh owner per exec so the phase starts at Planning each iteration.
716+
let (actor, handle) = Actor::spawn(
717+
None,
718+
KanbanActor::<TestBoard>::default(),
719+
board(KanbanColumn::Planning),
720+
)
721+
.await
722+
.expect("spawn");
723+
724+
let view = board(KanbanColumn::Planning);
725+
let mv =
726+
drive_scheduled_tick(&NextPhaseScheduler, &view, DatasetVersion(1), exec, &actor)
727+
.await
728+
.expect("scheduled ok")
729+
.expect("forward arc proposed + disposed");
730+
assert_eq!(mv.to, KanbanColumn::CognitiveWork);
731+
assert_eq!(
732+
mv.exec, exec,
733+
"scheduler's backend must survive, not be overwritten with Native"
734+
);
735+
736+
actor.stop(None);
737+
handle.await.expect("actor join");
738+
}
739+
}
692740
}

0 commit comments

Comments
 (0)