Summary
The simlin-engine has two independent circular-dependency detection gates that can disagree about whether a model is simulatable. This is an accepted-scope structural divergence (production uses one path; the other is a legacy test-harness-only path), but it is being deliberately widened by the forthcoming C-LEARN B1 fix and must be tracked so a future change either unifies the two gates or explicitly documents/removes the legacy one.
This item was surfaced during the C-LEARN B1 design (docs/clearn-investigation/B1-design.md; reviewer-priors.md §13.3 O1). O1 acceptance was explicitly conditioned on filing this rather than silently dropping the observation.
The two cycle-detection gates
System A -- production (salsa incremental compile)
compile_project_incremental -> assemble_simulation -> assemble_module -> model_dependency_graph_impl.
src/simlin-engine/src/db.rs:1134-1531
- Cycle detection at
db.rs:1316
- Gate /
NotSimulatable emission at db.rs:5004-5015
This is the path every production compile takes.
System B -- legacy, test-harness-only
project.rs::from_salsa -> model.rs::ModelStage1, which has its own CircularDependency, its own topo_sort, its own runlists, consumed by compiler/mod.rs / compiler/codegen.rs.
- Own
CircularDependency at src/simlin-engine/src/model.rs:442
pub(crate); exercised only by the model.rs / project.rs test harnesses, not by any production caller.
Why it matters / consequence
The forthcoming B1 fix (approach B -- element-level acyclicity refinement for whole-variable SCCs that are acyclic per element) targets System A only. After B1 lands:
- System A will correctly compile staged subscript-shift recurrences -- e.g. C-LEARN's
pevr / Target idiom, and the ref / interleaved / self_recurrence fixtures.
- System B will still reject the same models as circular (its
model.rs:442 CircularDependency is unchanged).
So any code or test that goes through System B will observe divergent simulatability behavior from production for exactly the class of models B1 is meant to unblock.
This divergence is an accepted scope decision:
- Production is System A; the C-LEARN production path does not exercise System B.
- Widening B1 to also refine System B's gate risks the 3-minute workspace test budget and is unrelated to the C-LEARN production goal.
It is accepted, but not free: the divergence must remain visible so a future change makes a deliberate choice -- either unify the two gates behind one implementation, or explicitly document System B as deprecated and/or remove it once nothing depends on it.
Components affected
src/simlin-engine/src/db.rs (System A: model_dependency_graph_impl, cycle at :1316, gate at :5004-5015)
src/simlin-engine/src/model.rs (System B: ModelStage1, CircularDependency at :442, topo_sort, runlists)
src/simlin-engine/src/project.rs (from_salsa, the System B entry point)
src/simlin-engine/src/compiler/mod.rs, src/simlin-engine/src/compiler/codegen.rs (System B consumers)
Possible approaches for resolution
- Unify: have System B (or its test harnesses) delegate cycle detection to the System A
model_dependency_graph_impl path, eliminating the second gate entirely.
- Deprecate/remove System B: if
model.rs::ModelStage1 / compiler::* is genuinely test-harness-only with no production role, migrate its tests onto the salsa path and delete the legacy path (the repo has no external users; only protobuf compatibility is load-bearing).
- Document the divergence as an explicit, intentional limitation in
src/simlin-engine/CLAUDE.md and at model.rs:442, with a note that System B is not expected to match System A on element-level-acyclic subscript-shift recurrences.
Option 1 or 2 is preferable long-term; option 3 is the minimum to avoid silent surprise.
Priority
Not urgent. Production correctness is unaffected (production = System A; B1 fixes System A). The risk is purely a latent footgun: a future contributor relying on System B's gate, or a test that asserts on it, would see behavior that contradicts production for B1-class models.
References
Discovery context
Identified during the C-LEARN B1 design (branch work for the pevr / Target staged subscript-shift recurrence). The B1 design and its reviewer priors explicitly require the dual-gate divergence to be filed as tracked tech-debt rather than dropped (reviewer-priors §13.3 O1).
Summary
The
simlin-enginehas two independent circular-dependency detection gates that can disagree about whether a model is simulatable. This is an accepted-scope structural divergence (production uses one path; the other is a legacy test-harness-only path), but it is being deliberately widened by the forthcoming C-LEARN B1 fix and must be tracked so a future change either unifies the two gates or explicitly documents/removes the legacy one.This item was surfaced during the C-LEARN B1 design (
docs/clearn-investigation/B1-design.md;reviewer-priors.md§13.3 O1). O1 acceptance was explicitly conditioned on filing this rather than silently dropping the observation.The two cycle-detection gates
System A -- production (salsa incremental compile)
compile_project_incremental->assemble_simulation->assemble_module->model_dependency_graph_impl.src/simlin-engine/src/db.rs:1134-1531db.rs:1316NotSimulatableemission atdb.rs:5004-5015This is the path every production compile takes.
System B -- legacy, test-harness-only
project.rs::from_salsa->model.rs::ModelStage1, which has its ownCircularDependency, its owntopo_sort, its own runlists, consumed bycompiler/mod.rs/compiler/codegen.rs.CircularDependencyatsrc/simlin-engine/src/model.rs:442pub(crate); exercised only by themodel.rs/project.rstest harnesses, not by any production caller.Why it matters / consequence
The forthcoming B1 fix (approach B -- element-level acyclicity refinement for whole-variable SCCs that are acyclic per element) targets System A only. After B1 lands:
pevr/Targetidiom, and theref/interleaved/self_recurrencefixtures.model.rs:442CircularDependencyis unchanged).So any code or test that goes through System B will observe divergent simulatability behavior from production for exactly the class of models B1 is meant to unblock.
This divergence is an accepted scope decision:
It is accepted, but not free: the divergence must remain visible so a future change makes a deliberate choice -- either unify the two gates behind one implementation, or explicitly document System B as deprecated and/or remove it once nothing depends on it.
Components affected
src/simlin-engine/src/db.rs(System A:model_dependency_graph_impl, cycle at :1316, gate at :5004-5015)src/simlin-engine/src/model.rs(System B:ModelStage1,CircularDependencyat :442,topo_sort, runlists)src/simlin-engine/src/project.rs(from_salsa, the System B entry point)src/simlin-engine/src/compiler/mod.rs,src/simlin-engine/src/compiler/codegen.rs(System B consumers)Possible approaches for resolution
model_dependency_graph_implpath, eliminating the second gate entirely.model.rs::ModelStage1/compiler::*is genuinely test-harness-only with no production role, migrate its tests onto the salsa path and delete the legacy path (the repo has no external users; only protobuf compatibility is load-bearing).src/simlin-engine/CLAUDE.mdand atmodel.rs:442, with a note that System B is not expected to match System A on element-level-acyclic subscript-shift recurrences.Option 1 or 2 is preferable long-term; option 3 is the minimum to avoid silent surprise.
Priority
Not urgent. Production correctness is unaffected (production = System A; B1 fixes System A). The risk is purely a latent footgun: a future contributor relying on System B's gate, or a test that asserts on it, would see behavior that contradicts production for B1-class models.
References
docs/clearn-investigation/B1-design.md§5a, §9docs/clearn-investigation/reviewer-priors.md§12.3, §13.3 (O1 -- this issue is the explicit acceptance condition for O1)main-variableCircularDependencyonprevious_emissions_intensity_vs_refyrthat B1 addresses on System A). engine: C-LEARN full simulation blocked by non-macro issues (circular dep, dimension mismatches, unknown dep) -- the macro epic's deferred 'tracked separately' follow-up #559 tracks the symptom/fix; this issue tracks the structural dual-gate divergence the fix introduces. Distinct, not a duplicate.Discovery context
Identified during the C-LEARN B1 design (branch work for the
pevr/Targetstaged subscript-shift recurrence). The B1 design and its reviewer priors explicitly require the dual-gate divergence to be filed as tracked tech-debt rather than dropped (reviewer-priors §13.3 O1).