fix: no early cutoff between documents in the same sub-decision cycle

Inside a decision-call cycle a recorded signature depends on which member was cut off as recursive, so comparing it could skip a dependent (seen when undoing an edit in a 3-graph cycle).
This commit is contained in:
Stefan
2026-10-02 08:38:20 +02:00
parent f62eb9a8ed
commit 8578006ea3
2 changed files with 40 additions and 0 deletions
+6
View File
@@ -7,6 +7,7 @@ use crate::workspace::reads::ReadView;
impl Db {
pub(crate) fn walk(&self, changed: &[Arc<str>]) -> Vec<Arc<str>> {
let frozen = self.frozen_views();
let cycles = self.document_dependencies().signature_cycles();
self.document_dependencies()
.affected(changed, |user, dependency, kind| {
let recorded = frozen
@@ -20,8 +21,13 @@ impl Db {
| (DependencyKind::Signature, ReadView::Signature(_))
)
});
let cyclic = kind == DependencyKind::Signature
&& cycles
.get(user)
.is_some_and(|component| cycles.get(dependency) == Some(component));
match (kind, recorded) {
(DependencyKind::Import, _) | (_, None) => true,
_ if cyclic => true,
(_, Some(view)) => {
self.graph_stack.borrow_mut().push(user.clone());
let holds = self.view_holds(dependency, view);
@@ -2,6 +2,8 @@ use std::collections::VecDeque;
use std::sync::Arc;
use ahash::{HashMap, HashSet};
use petgraph::algo::tarjan_scc;
use petgraph::graph::{DiGraph, NodeIndex};
use zen_types::decision::DecisionNodeKind;
use crate::model::DecisionContent;
@@ -94,6 +96,38 @@ impl DependencyIndex {
out
}
pub fn signature_cycles(&self) -> HashMap<Arc<str>, usize> {
let mut graph: DiGraph<Arc<str>, ()> = DiGraph::new();
let mut nodes: HashMap<Arc<str>, NodeIndex> = HashMap::default();
let mut node = |graph: &mut DiGraph<Arc<str>, ()>, path: &Arc<str>| {
*nodes
.entry(path.clone())
.or_insert_with(|| graph.add_node(path.clone()))
};
for (user, uses) in &self.uses {
for (dependency, kind) in uses {
if *kind == DependencyKind::Signature {
let from = node(&mut graph, user);
let to = node(&mut graph, dependency);
graph.add_edge(from, to, ());
}
}
}
let mut out: HashMap<Arc<str>, usize> = HashMap::default();
for (id, component) in tarjan_scc(&graph).into_iter().enumerate() {
let cyclic = component.len() > 1
|| component
.first()
.is_some_and(|&n| graph.contains_edge(n, n));
if cyclic {
for n in component {
out.insert(graph[n].clone(), id);
}
}
}
out
}
fn declared(content: &DecisionContent) -> Vec<Dependency> {
let mut out: Vec<Dependency> = Vec::new();
let mut push = |path: &Arc<str>, kind: DependencyKind| {