feat(LF-12): Pipeline DAG with StepId derivation + OrchestrationBridge adapter#300
Conversation
Add `StepId` type alias and `id`/`depends_on` fields to `UnifiedStep` in the contract crate, enabling DAG-based step dependency tracking. Implement topological executor in `lance-graph-planner::pipeline` using Kahn's algorithm with cycle detection, missing-dep validation, and execute_with callback. 10 tests cover empty DAG, single step, linear chain, diamond, cycle detection (2-node and 3-node), missing dependency, duplicate StepId, step failure halting, and wide fan-out. Unblocks: PR-F4, PR-G2, PR-G6 https://claude.ai/code/session_01NYGrxVopyszZYgLBxe4hgj
…dmine Closes the three loose ends called out by the S1 reviewer on the PipelineDag PR: 1. (Tier 1) Remove the `pub id: StepId` field from `UnifiedStep`. Every caller was hard-coding `id: 0`, collapsing the DAG to a single colliding node. Replace with a derived `id()` method that FNV-1a-hashes `step_id`. Two distinct step_id strings now produce distinct numeric ids deterministically. All 4 callers (cypher_bridge.rs, serve.rs, lance_membrane.rs, orchestration_impl.rs) drop the `id: 0` line; `pipeline.rs` calls `step.id()` instead of `step.id`. The type alias `pub type StepId = u64` stays — pipeline keys its DAG and dependency edges off it. 2. (Tier 2) Add `PipelineDag::execute_via_bridge<B: OrchestrationBridge>` adapter so PR-F4 / PR-G2 cross-domain routing does not fork into a parallel closure-based executor. Tests cover end-to-end routing through a counting bridge. 3. (Tier 2) Add `cycle_detected_self_loop` test for the 1 -> 1 case; `CycleDetected.involved` now provably contains the self-looping step. Critical: the regression test `test_distinct_step_ids_when_step_id_strings_differ` was written FIRST and verified to fail under the old design (compile error: `id` is a field, not a method) before the fix landed. Tests: contract 307 pass (was 306); planner 182 pass (was 170, +12 in pipeline module covering the three loose ends); callcenter 23 pass; cognitive-shader-driver 46 pass (standalone build). https://claude.ai/code/session_01NYGrxVopyszZYgLBxe4hgj
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5e568c34e4
ℹ️ 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".
| pub fn id(&self) -> StepId { | ||
| // FNV-1a over step_id bytes — deterministic, collision-resistant. | ||
| let mut h: u64 = 0xcbf29ce484222325; | ||
| for b in self.step_id.as_bytes() { | ||
| h ^= *b as u64; |
There was a problem hiding this comment.
Keep StepId stable after DAG construction
UnifiedStep::id() is recomputed from the mutable step_id string, but PipelineDag::build caches IDs once in index and later APIs (get, error reporting, execution_order) assume that identity is stable. Because execute_with passes &mut UnifiedStep to user/bridge callbacks, any callback that normalizes or rewrites step_id will desynchronize the cached index from the step’s current ID, causing lookups by current ID to fail and failures to be attributed to a different ID than the one validated in the DAG.
Useful? React with 👍 / 👎.
- Fix 4× `#[deprecated(since = "next")]` invalid semver in context_chain.rs — drop `since` field (G3 refactor artifact) - Fix `actor.role <= u8::MAX` tautological comparison in lance_membrane.rs:768 — replace with meaningful `< 32` guard - Document Wave-1 LOC audit in EPIPHANIES.md: recovery (#275-#283) = +8,728; Wave 1 (#300-#306) = +3,156; combined = +11,807; zero LOC lost from G1 rebase cargo fmt --check: clean cargo clippy (4 crates): warnings only, 0 errors https://claude.ai/code/session_01NYGrxVopyszZYgLBxe4hgj
Summary
Vec<UnifiedStep>withdepends_onDAG edgesUnifiedStep::id()computes FNV-1a overstep_idbytes — eliminates theid: 0landmine across all 4 callers (cypher_bridge, serve, lance_membrane, orchestration_impl)execute_via_bridge<B>(&self, bridge: &B)wires PipelineDag into the canonical contract pattern — F4/G2 won't fork the executorPipelineError::{MissingDependency, CycleDetected, StepFailed, DuplicateStepId}Review notes
type StepId = u64alias kept in contract;depends_on: Vec<StepId>added to UnifiedStepTest plan
test_distinct_step_ids_when_step_id_strings_differ— regression test (failing-first proven on oldid: 0code)https://claude.ai/code/session_01NYGrxVopyszZYgLBxe4hgj
Generated by Claude Code