S3 IN-leg driver — version tick → owner forward-arc advance (no-op suppressed)#579
Conversation
… advance
The actor-side half of S3 (E-SUBSTRATE-IS-THE-SCHEDULER), mirroring the S2
atomic MulAdvance pattern in lance-graph-supervisor::kanban_actor:
- KanbanMsg::Tick { at, reply } — atomic in-actor realization of the contract's
NextPhaseScheduler: a substrate version tick advances the owner along the
Rubicon forward arc (phase().next_phases().first()) in ONE serialized message,
reading the phase at the instant of mutation. Absorbing column -> None: the
no-op tick is suppressed (forward arc is legal by construction, so the
infallible advance_phase is used; no error path). This applies the codex #578
atomicity lesson to the IN-leg.
- drive_version_tick(actor, at) — thin async wrapper over Tick.
- drive_scheduled_tick(scheduler, view, at, exec, actor) — generic consumer that
drives the EXISTING VersionScheduler trait ("propose, don't dispose": the
scheduler proposes from a view, the owner disposes via Advance, None
suppresses). For custom policies that read a richer view than the owner
computes internally; documented as advisory (proposal computed outside the
owner message, so it may relay a typed Illegal rather than corrupt).
+3 tests (12 passed/0 failed under --features supervisor --lib): forward-arc
chain then suppress at absorbing; two concurrent ticks serialize along the arc
(no stale-phase collision); generic consumer drives NextPhaseScheduler
propose->dispose + suppresses an absorbing proposal. clippy + fmt clean; light
build (no lance/datafusion, no disk gate).
Remaining S3 (lance/disk-gated): wire the LIVE
LanceVersionScheduler::drive_at_latest over a real VersionedGraph::versions() to
feed `at`. The apply + no-op-suppress loop is done; only the live versions()
poll remains.
Board hygiene: plan S3 status annotated; AGENT_LOG cont.34 prepended.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CcpLeEC3XK8Eye53GKBVvi
|
Warning Review limit reached
More reviews will be available in 51 minutes and 18 seconds. Learn how PR review limits work. Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file). ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits. 🚦 How do rate limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (3)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2f5ed3d0f9
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| let inner = ractor::call!(actor, |reply| KanbanMsg::Advance { | ||
| to: proposed.to, | ||
| reply |
There was a problem hiding this comment.
Preserve non-Native exec targets in scheduled moves
When callers use drive_scheduled_tick with ExecTarget::Jit, SurrealQl, or Elixir, scheduler.on_version(...) stamps that backend onto the proposed KanbanMove, but this RPC sends only proposed.to and returns the owner-generated move instead. The current owner implementations hard-code ExecTarget::Native, so the scheduled move is reported/routed as Native even though the scheduler selected a different backend.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in fd1d58ff. drive_scheduled_tick now keeps the owner's emitted move (authoritative phase transition, witness position, libet anchor — from the real mutation) but overlays proposed.exec, so a Jit/SurrealQl/Elixir selection survives instead of being flattened to the owner's Native default. Regression test scheduled_tick_preserves_non_native_exec_target asserts all three non-Native targets round-trip (13 tests green, clippy + fmt clean).
(Note: the atomic drive_version_tick/Tick path is unaffected — it has no scheduler choosing a backend, so the owner's Native is correct there.)
Generated by Claude Code
…ck (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
What
The actor-side half of S3 (the IN-direction Rubicon seam,
E-SUBSTRATE-IS-THE-SCHEDULER), mirroring the S2 atomicMulAdvancepattern in
lance-graph-supervisor::kanban_actor(featuresupervisor).A substrate version tick → next legal
KanbanMove→ owner applies it →no-op ticks suppressed. The OUT-leg already had the owner-advance (#576),
delivery edge (#577), and S2 MUL driver (#578); this is the IN-leg driver.
How
KanbanMsg::Tick { at, reply }— the atomic in-actor realization ofthe contract's
NextPhaseScheduler: a version tick advances the owner alongthe Rubicon forward arc (
phase().next_phases().first()) in ONE serializedmessage, reading the phase at the instant of mutation. Absorbing column
(
Commit/Prune) →None: the no-op tick is suppressed, not an error.No error variant — the forward arc is legal by construction, so the infallible
advance_phaseis correct here. This applies the codex feat(supervisor): S2 driver — MUL gate → owner advance (drive_mul_advance) #578 atomicitylesson to the IN-leg (no stale-phase TOCTOU window).
drive_version_tick(actor, at)— thin async wrapper overTick.drive_scheduled_tick(scheduler, view, at, exec, actor)— generic consumerthat drives the existing
VersionSchedulertrait ("propose, don'tdispose": the scheduler proposes from a view, the owner disposes via
Advance,Nonesuppresses). For custom policies (version-delta gating,Plan/Pruneover the forward arc, batching) that read a richer view than theowner computes internally; documented as advisory — the proposal is
computed outside the owner message, so it may relay a typed
Illegalratherthan silently corrupt.
No new contract types — composes the existing
VersionScheduler/NextPhaseScheduler/MailboxSoaView/MailboxSoaOwnersurface.Tests (+3, light — no lance/datafusion/disk)
cargo test -p lance-graph-supervisor --features supervisor --lib→ 12 passed / 0 failedversion_tick_advances_forward_arc_then_suppresses_at_absorbing— Planning→CognitiveWork→Evaluation→Commit, then the next tick is a suppressed no-op (phase staysCommit).concurrent_version_ticks_serialize_along_the_arc— two concurrent ticks chain along the arc, neither lost, no stale-phase collision.custom_scheduler_proposes_and_owner_disposes— drivesNextPhaseSchedulerpropose→dispose and suppresses an absorbing proposal.clippy clean (no supervisor-crate warnings — only pre-existing ontology/callcenter), fmt clean.
Remaining S3 (lance/disk-gated, not in this PR)
Wire the LIVE source —
lance-graph::graph::scheduler::LanceVersionScheduler::drive_at_latestover a real
VersionedGraph::versions()— to feedatintodrive_version_tick(or a custom policy into
drive_scheduled_tick). The apply + no-op-suppress loopis done; only the live
versions()poll remains.Board hygiene
.claude/plans/capstone-out-leg-wiring-v1.md— S3 status annotated (apply+suppress driver shipped; remaining = live source)..claude/board/AGENT_LOG.md— cont.³⁴ prepended.🤖 Generated with Claude Code
Generated by Claude Code