Skip to main content

adam_rs/
sheet.rs

1//! The [`Sheet`] owns and manages a property model constraint graph.
2//!
3//! All cells and relationships are created through the sheet and are
4//! destroyed when the sheet is dropped.
5//!
6//! Queries borrow declarations or describe a completed propagation; they do not
7//! propagate implicitly. This graph combines a conditional relationship, a fixed
8//! writer, a live source filter and a requirement.
9//!
10//! ```
11//! use adam_rs::{CellKind, Filter, MatchExpr, Method, Requirement, Sheet};
12//! use std::any::TypeId;
13//! let mut sheet = Sheet::default();
14//! let mode = sheet.add_source(true);
15//! let limit = sheet.add_source(100_i32);
16//! let a = sheet.add_cell(7_i32);
17//! let b = sheet.add_cell(0_i32);
18//! let out = sheet.add_cell(0_i32);
19//! sheet.add_filter(a, Filter::range(TypeId::of::<i32>(), vec![limit],
20//!     vec![TypeId::of::<i32>()],
21//!     |v, args| Ok(Box::new((*v.downcast_ref::<i32>().unwrap())
22//!         .clamp(0, *args[0].downcast_ref::<i32>().unwrap()))),
23//!     |args| Some((Box::new(0_i32), Box::new(*args[0].downcast_ref::<i32>()?)))))?;
24//! let rel = sheet.add_relationship(vec![Method::from_fn_1_1(a, b, |v: &i32| Ok(*v))])?;
25//! let conditional = sheet.add_conditional(MatchExpr::cell(mode),
26//!     vec![(vec![true], vec![rel])], vec![])?;
27//! sheet.add_out(Method::from_fn_1_1(b, out, |v: &i32| Ok(*v)), vec![])?;
28//! let check = sheet.add_requirement(b, Requirement::from_fn_1(b, |v: &i32| Ok(*v >= 0)))?;
29//! sheet.validate()?;
30//! sheet.write(a, 9_i32)?;
31//! sheet.propagate()?;
32//! assert_eq!(*sheet.read::<i32>(out)?, 9);
33//! assert_eq!(*sheet.source::<i32>(a)?, 9);
34//! assert_eq!(sheet.cell_type_id(a), Some(TypeId::of::<i32>()));
35//! assert_eq!(sheet.cell_kind(out), Some(CellKind::Out));
36//! assert_eq!(sheet.cells().count(), 5);
37//! assert_eq!(sheet.relationships().count(), 2);
38//! assert_eq!(sheet.cell_adj(b).unwrap().len(), 2);
39//! assert_eq!(sheet.relationship_adj(rel), Some([a, b].as_slice()));
40//! assert_eq!(sheet.method_inputs(rel, 0), Some([a].as_slice()));
41//! assert_eq!(sheet.method_outputs(rel, 0), Some([b].as_slice()));
42//! assert_eq!(sheet.selected_method(rel), Some(0));
43//! assert!(sheet.is_source(a));
44//! assert!(sheet.is_forced(b));
45//! assert!(sheet.forced_cells().any(|id| id == b));
46//! assert!(sheet.is_relationship_forced(rel));
47//! assert!(sheet.forced_relationships().any(|id| id == rel));
48//! assert_eq!(sheet.conditionals().collect::<Vec<_>>(), [conditional]);
49//! assert_eq!(sheet.out_cells().collect::<Vec<_>>(), [out]);
50//! assert_eq!(sheet.conditional_match_cells(conditional), Some([mode].as_slice()));
51//! assert_eq!(sheet.conditional_branch_count(conditional), Some(1));
52//! assert_eq!(sheet.conditional_branch_relationships(conditional, 0), Some([rel].as_slice()));
53//! assert_eq!(sheet.conditional_default_relationships(conditional), Some([].as_slice()));
54//! assert_eq!(sheet.conditional_active_branch(conditional)?, Some(0));
55//! assert_eq!(sheet.filter_args(a), Some([limit].as_slice()));
56//! assert_eq!(sheet.filter_dependents(limit), [a]);
57//! assert!(matches!(sheet.filter_kind(a), Some(adam_rs::FilterKind::Range { .. })));
58//! assert_eq!(sheet.filter_range::<i32>(a), Some((0, 100)));
59//! assert!(sheet.filter_violation(a).is_none());
60//! assert_eq!(sheet.filter_violated_cells().count(), 0);
61//! assert!(sheet.filter_violation_cells().is_empty());
62//! assert_eq!(sheet.cell_requirements(b), Some([check].as_slice()));
63//! assert_eq!(sheet.requirement_cell(check), Some(b));
64//! assert_eq!(sheet.requirement_inputs(check), Some([b].as_slice()));
65//! assert!(sheet.cell_requirements_valid(b));
66//! assert_eq!(sheet.violated_requirements(b).count(), 0);
67//! assert!(sheet.contributing_cells(b).contains(&limit));
68//! assert!(sheet.requirement_contributing_cells(check).contains(&a));
69//! assert!(sheet.requirement_relevant_cells().contains(&a));
70//! assert!(sheet.requirement_violation_cells().is_empty());
71//! assert!(sheet.changed().any(|id| id == out));
72//! sheet.clear_changed();
73//! assert_eq!(sheet.changed().count(), 0);
74//! # Ok::<(), adam_rs::Error>(())
75//! ```
76//!
77//! # Forced, self-referencing, and reversible outcomes
78//!
79//! A cell has three distinct outcomes when a relationship determines it:
80//!
81//! - **Conditionally forced.** The forcing relationship supplies the effective
82//!   value, [`Sheet::read`] reports it, and the cell's [`Sheet::source`]
83//!   aspiration is unchanged. The cell keeps the priority it had when forcing
84//!   began, and an explicit write while forced replaces that priority with the
85//!   write's recency. When the last forcing cause ends, the retained aspiration
86//!   and priority compete in the first plan that can use the cell, so no
87//!   additional edit is needed.
88//! - **Self-referencing.** A method that reads and writes the same cell seeds
89//!   from its aspiration and honors that edit's priority. Forcing and release
90//!   do not erase either.
91//! - **Ordinary reversible.** A losing cell receives the winner's value as its
92//!   new aspiration with a derived priority. It does not revive an earlier
93//!   value when the relationship later deactivates, and forcing a cell that
94//!   was already derived retains that derived value and priority.
95//!
96//! ```
97//! use adam_rs::{MatchExpr, Method, Sheet};
98//! let mut sheet = Sheet::default();
99//! let mode = sheet.add_cell(0_i32);
100//! let input = sheet.add_cell(30_i32);
101//! let a = sheet.add_cell(1_i32);
102//! let b = sheet.add_cell(2_i32);
103//! let force = sheet.add_relationship(vec![Method::from_fn_1_1(input, a, |v: &i32| Ok(*v))])?;
104//! let reversible = sheet.add_relationship(vec![
105//!     Method::from_fn_1_1(a, b, |v: &i32| Ok(*v)),
106//!     Method::from_fn_1_1(b, a, |v: &i32| Ok(*v)),
107//! ])?;
108//! sheet.add_conditional(
109//!     MatchExpr::cell(mode),
110//!     vec![(vec![1_i32], vec![force]), (vec![2_i32], vec![reversible])],
111//!     vec![],
112//! )?;
113//! sheet.write(b, 22_i32)?;
114//! sheet.write(a, 11_i32)?;
115//! sheet.write(mode, 1_i32)?;
116//! sheet.propagate()?;
117//! assert!(sheet.is_forced(a));
118//! assert_eq!((*sheet.read::<i32>(a)?, *sheet.source::<i32>(a)?), (30, 11));
119//!
120//! // Releasing `a` keeps its newer edit, so `a` wins the reversible relationship.
121//! sheet.write(mode, 2_i32)?;
122//! sheet.propagate()?;
123//! assert!(sheet.is_source(a));
124//! assert_eq!(*sheet.read::<i32>(b)?, 11);
125//! # Ok::<(), adam_rs::Error>(())
126//! ```
127
128use std::any::{Any, TypeId};
129use std::collections::{HashMap, HashSet};
130use std::rc::Rc;
131
132use slotmap::SlotMap;
133
134use crate::{
135    cell::{CellData, CellId, CellKind},
136    conditional::{Branch, ConditionalData, ConditionalId, MatchExpr, MatchSource},
137    error::{Error, ErrorSite},
138    filter::{Filter, FilterKind, FilterViolation},
139    planner::{Plan, PlanStep, SeedSignatures, SeedSource, Seeds},
140    relationship::{Method, RelationshipData, RelationshipId},
141    requirement::{Requirement, RequirementData, RequirementId},
142};
143
144mod cached;
145mod dependency;
146mod prerequisites;
147
148#[cfg(test)]
149use cached::ReuseStats;
150use cached::{CachedPlan, PreparedPlan};
151
152/// Owns a complete property model constraint graph.
153///
154/// Create cells with [`Sheet::add_cell`], define multi-way constraints with
155/// [`Sheet::add_relationship`], write input values with [`Sheet::write`],
156/// then call [`Sheet::propagate`] to update derived cells. Propagation automatically
157/// reuses eligible prepared plans or plans again when necessary.
158///
159/// After a mutating operation returns an error, the sheet's state is unspecified.
160/// Do not inspect or reuse it; discard it and rebuild from host-owned inputs.
161///
162/// # Example
163///
164/// ```rust
165/// use adam_rs::{Sheet, Method};
166///
167/// let mut sheet = Sheet::new();
168/// let a = sheet.add_cell(0_i32);
169/// let b = sheet.add_cell(0_i32);
170/// sheet.add_relationship(vec![Method::from_fn_1_1(a, b, |x: &i32| Ok(*x * 2))]).unwrap();
171/// sheet.write(a, 3_i32).unwrap();
172/// assert_eq!(*sheet.read::<i32>(a).unwrap(), 3);
173/// ```
174pub struct Sheet {
175    pub(crate) cells: SlotMap<CellId, CellData>,
176    pub(crate) relationships: SlotMap<RelationshipId, RelationshipData>,
177    pub(crate) changed_cells: Vec<CellId>,
178    /// Monotonic counter incremented by both `add_cell` and `write`; cells added
179    /// later and cells written later have strictly higher strength, making the
180    /// default method-selection direction deterministic.
181    next_strength: u64,
182    /// The prepared snapshot from the last successful `propagate()` call, retained so the
183    /// display-only accessors ([`Sheet::is_source`], [`Sheet::selected_method`],
184    /// [`Sheet::is_forced`]) can report which method the planner picked for each
185    /// relationship without re-planning.
186    last_plan: Option<Rc<PreparedPlan>>,
187    /// Number of cells with a priority retained through conditional forcing.
188    remembered_strength_count: usize,
189    /// Eligible prepared unconditional plan, independent of the display snapshot.
190    pre_plan_cache: Option<CachedPlan>,
191    /// Eligible prepared final active plan, independent of the unconditional plan.
192    main_plan_cache: Option<CachedPlan>,
193    /// Ordered method signatures retained until the graph structure changes.
194    seed_signatures: Option<Rc<SeedSignatures>>,
195    /// Counts actual preparation and reuse-validation work for this sheet only.
196    #[cfg(test)]
197    reuse_stats: ReuseStats,
198    /// All conditionals registered on this sheet.
199    pub(crate) conditionals: SlotMap<ConditionalId, ConditionalData>,
200    /// Union of all RelationshipIds assigned to any conditional branch or default.
201    /// Used to exclude them from the unconditional active set.
202    pub(crate) conditional_relationships: HashSet<RelationshipId>,
203    /// All requirements registered on this sheet, across all cells.
204    requirements: SlotMap<RequirementId, RequirementData>,
205    /// Requirements that evaluated `false` as of the last `propagate()` call, grouped
206    /// by cell. Sparse: a cell with no entry had all its requirements hold.
207    last_requirement_violations: HashMap<CellId, Vec<RequirementId>>,
208    /// Filter violations recorded against a derived value as of the last `propagate()`
209    /// call.
210    last_filter_violations: HashMap<CellId, FilterViolation>,
211    /// Reverse index of `filter_args`: for each cell, the live cells whose filter
212    /// references it as one of its dynamic arguments. Built incrementally in
213    /// `add_filter`; cells and filters are never removed once added, so this needs no
214    /// invalidation, matching every other per-cell set/map `Sheet` already maintains
215    /// for its own lifetime.
216    filter_dependents: HashMap<CellId, Vec<CellId>>,
217    /// Whether static guard independence has been validated for the current structure.
218    guard_independent: bool,
219}
220
221/// A conditional's evaluated match value: borrowed (existing cell, no allocation) or owned
222/// (freshly computed by a [`MatchExpr`] function).
223enum MatchValue<'a> {
224    Ref(&'a dyn Any),
225    Owned(Box<dyn Any>),
226}
227
228impl MatchValue<'_> {
229    /// Returns the contained value as a type-erased reference, regardless of variant.
230    fn as_dyn(&self) -> &dyn Any {
231        match self {
232            MatchValue::Ref(r) => *r,
233            MatchValue::Owned(b) => b.as_ref(),
234        }
235    }
236}
237
238/// Provenance needed to decide whether a staged guard-cone step can be reused.
239///
240/// Reuse is intentionally conservative: a step is reusable only when the final plan selects the
241/// same method, producer path, source/derived classification, and seed inputs. A mismatch returns
242/// `Error::Conflict` rather than guessing a compatible assignment or reusing stale values.
243#[derive(Clone, PartialEq, Eq)]
244struct StepProvenance {
245    input_producers: Vec<Option<PlanStep>>,
246    seed_inputs: Vec<CellId>,
247    source_producer: Option<PlanStep>,
248    output_classes: Vec<(CellId, OutputClassification)>,
249}
250
251/// Whether a selected step writes a cell's staged value as source or derived.
252#[derive(Clone, Copy, PartialEq, Eq)]
253enum OutputClassification {
254    /// The write replaces the staged source value.
255    Source,
256    /// The write shadows the staged source value.
257    Derived,
258}
259
260/// Selected producer paths for every step and output in one execution plan.
261#[derive(Default)]
262struct PlanProvenance {
263    steps: HashMap<PlanStep, StepProvenance>,
264    output_producers: HashMap<CellId, PlanStep>,
265}
266
267/// Transactional propagation state whose writes remain private until commit.
268///
269/// Staged derived values override staged sources, which override live sources.
270/// Each replaced source receives a fresh version; absent sources have version zero.
271/// `changed` contains each written cell exactly once and has the same members as
272/// `changed_set`. Dropping an uncommitted stage discards every owned result.
273#[derive(Default)]
274struct PropagationStage {
275    sources: HashMap<CellId, Box<dyn Any>>,
276    /// Version of each staged source value; an absent cell reads its live source (version 0).
277    source_versions: HashMap<CellId, u64>,
278    last_source_version: u64,
279    derived: HashMap<CellId, Box<dyn Any>>,
280    executed_methods: HashSet<(RelationshipId, usize)>,
281    executed_filters: HashSet<CellId>,
282    step_provenance: HashMap<PlanStep, StepProvenance>,
283    changed: Vec<CellId>,
284    changed_set: HashSet<CellId>,
285}
286
287impl PropagationStage {
288    /// Returns the staged effective value for `id`, ignoring any live sheet `derived`.
289    fn effective<'a>(&'a self, cells: &'a SlotMap<CellId, CellData>, id: CellId) -> &'a dyn Any {
290        self.derived
291            .get(&id)
292            .map(|value| value.as_ref())
293            .or_else(|| self.sources.get(&id).map(|value| value.as_ref()))
294            .unwrap_or_else(|| cells[id].source.as_ref())
295    }
296
297    /// Returns the staged source value for `id`, ignoring any live sheet `derived`.
298    fn source<'a>(&'a self, cells: &'a SlotMap<CellId, CellData>, id: CellId) -> &'a dyn Any {
299        self.sources
300            .get(&id)
301            .map(|value| value.as_ref())
302            .unwrap_or_else(|| cells[id].source.as_ref())
303    }
304
305    /// Returns the staged source value for `id` with a version identifying that value.
306    ///
307    /// - Postcondition: two calls return equal versions only if no staged source write or
308    ///   reclassification changed `id`'s source value between them.
309    fn seed_source<'a>(
310        &'a self,
311        cells: &'a SlotMap<CellId, CellData>,
312        id: CellId,
313    ) -> SeedSource<'a> {
314        SeedSource {
315            value: self.source(cells, id),
316            version: self.source_versions.get(&id).copied().unwrap_or(0),
317        }
318    }
319
320    /// Records a new staged source value for `id` under a fresh version.
321    fn insert_source(&mut self, id: CellId, value: Box<dyn Any>) {
322        self.last_source_version += 1;
323        self.source_versions.insert(id, self.last_source_version);
324        self.sources.insert(id, value);
325    }
326
327    /// Shadows the source for derived writes or replaces it for ordinary writes.
328    fn write(&mut self, id: CellId, value: Box<dyn Any>, shadow: bool) {
329        if shadow {
330            self.derived.insert(id, value);
331        } else {
332            self.insert_source(id, value);
333            self.derived.remove(&id);
334        }
335        if self.changed_set.insert(id) {
336            self.changed.push(id);
337        }
338    }
339
340    /// Reclassifies an existing staged output for the general plan's shadowing rule.
341    ///
342    /// - Precondition: `id` has a staged source or derived value.
343    fn reclassify(&mut self, id: CellId, shadow: bool) {
344        if shadow {
345            if let Some(value) = self.sources.remove(&id) {
346                self.source_versions.remove(&id);
347                self.derived.insert(id, value);
348            }
349        } else if let Some(value) = self.derived.remove(&id) {
350            self.insert_source(id, value);
351        }
352    }
353}
354
355impl Sheet {
356    /// Creates an empty sheet with no cells or relationships.
357    ///
358    /// # Examples
359    /// See [`Sheet`]'s example.
360    pub fn new() -> Self {
361        Sheet {
362            cells: SlotMap::with_key(),
363            relationships: SlotMap::with_key(),
364            changed_cells: Vec::new(),
365            next_strength: 0,
366            last_plan: None,
367            remembered_strength_count: 0,
368            pre_plan_cache: None,
369            main_plan_cache: None,
370            seed_signatures: None,
371            #[cfg(test)]
372            reuse_stats: ReuseStats::default(),
373            conditionals: SlotMap::with_key(),
374            conditional_relationships: HashSet::new(),
375            requirements: SlotMap::with_key(),
376            last_requirement_violations: HashMap::new(),
377            last_filter_violations: HashMap::new(),
378            filter_dependents: HashMap::new(),
379            guard_independent: true,
380        }
381    }
382
383    /// Registers a cell with an initial value and returns a stable handle.
384    ///
385    /// The cell's `TypeId` is fixed at creation time; subsequent `write` and
386    /// `read` calls that use a different type will return `Error::TypeMismatch`.
387    ///
388    /// Each call increments the sheet's internal strength counter and sets bit 63
389    /// of the result. This partitions the strength space: written/added cells always
390    /// have higher strength than derived cells, ensuring stability across conditional
391    /// branch switches.
392    ///
393    /// - Complexity: Amortized O(1), plus O(A) when invalidation releases the final
394    ///   owners of prepared artifacts of total size A.
395    ///
396    /// # Examples
397    /// See the [graph example](crate::sheet).
398    pub fn add_cell<T: Any + PartialEq + 'static>(&mut self, value: T) -> CellId {
399        self.invalidate_prepared_plans();
400        self.next_strength += 1;
401        let strength = self.next_strength | (1u64 << 63);
402        self.cells.insert(CellData {
403            source: Box::new(value),
404            derived: None,
405            type_id: TypeId::of::<T>(),
406            strength,
407            remembered_strength: None,
408            changed: false,
409            adj: Vec::new(),
410            eq_fn: |a, b| a.downcast_ref::<T>() == b.downcast_ref::<T>(),
411            filter: None,
412            kind: CellKind::Cell,
413            requirements: Vec::new(),
414        })
415    }
416
417    /// Registers a cell that can never be claimed as any method's output — always a
418    /// planner source, forever.
419    ///
420    /// - Complexity: Amortized O(1), plus O(A) when invalidation releases the final
421    ///   owners of prepared artifacts of total size A.
422    ///
423    /// # Examples
424    /// See the [graph example](crate::sheet).
425    pub fn add_source<T: Any + PartialEq + 'static>(&mut self, value: T) -> CellId {
426        let id = self.add_cell(value);
427        self.cells[id].kind = CellKind::Source;
428        id
429    }
430
431    /// Returns the fixed concrete type of a live cell, or `None` for an invalid ID.
432    ///
433    /// # Examples
434    ///
435    /// ```
436    /// use std::any::TypeId;
437    /// use adam_rs::{CellId, Sheet};
438    /// let mut sheet = Sheet::new();
439    /// let cell = sheet.add_cell(7i32);
440    /// assert_eq!(sheet.cell_type_id(cell), Some(TypeId::of::<i32>()));
441    /// assert_eq!(sheet.cell_type_id(CellId::default()), None);
442    /// ```
443    pub fn cell_type_id(&self, id: CellId) -> Option<TypeId> {
444        self.cells.get(id).map(|cell| cell.type_id)
445    }
446
447    /// Returns `id`'s fixed cell kind.
448    ///
449    /// Returns `None` if `id` is not a live cell in this sheet.
450    ///
451    /// # Examples
452    /// See the [graph example](crate::sheet).
453    pub fn cell_kind(&self, id: CellId) -> Option<CellKind> {
454        self.cells.get(id).map(|c| c.kind)
455    }
456
457    /// Registers a relationship defined by a non-empty list of methods.
458    ///
459    /// All methods are validated: their declared `TypeId`s must match the
460    /// registered cells, and each method must have at least one output. A method
461    /// with no inputs is explicitly allowed: it defines a fixed point (a constant,
462    /// independent of every other cell) rather than a derivation.
463    /// On success the `RelationshipId` is added to each adjacent cell's adjacency list.
464    ///
465    /// A cell that appears in both a method's inputs and its outputs is a self-referencing
466    /// cell and is explicitly allowed.
467    ///
468    /// # Errors
469    ///
470    /// - `Error::InvalidMethod` — `methods` is empty, or a method has no outputs.
471    /// - `Error::MismatchedMethodCells` — some method's `inputs ∪ outputs` differs
472    ///   from another method's in the same relationship.
473    /// - `Error::InvalidMethodOutputs` — a method's own `outputs` list names a cell
474    ///   more than once, or two methods in the same relationship have identical or
475    ///   nested `outputs` sets. Overlapping non-nested output sets are allowed.
476    /// - `Error::InvalidId` — a `CellId` in any method is not found in this sheet.
477    /// - `Error::TypeMismatch` — a method's declared `TypeId` does not match the
478    ///   cell's registered `TypeId`.
479    /// - `Error::InvalidCellKind` — a method's output cell is `Source`- or `Out`-kind.
480    ///
481    /// Guard independence is checked by [`Sheet::validate`] and by [`Sheet::propagate`],
482    /// not by this mutator.
483    ///
484    /// - Complexity: O(m² × c) where m is the total number of methods and c is the
485    ///   maximum number of cells per method, due to pairwise output-set comparison,
486    ///   plus O(A) when invalidation releases the final owners of prepared artifacts
487    ///   of total size A.
488    ///
489    /// # Examples
490    /// See the [graph example](crate::sheet).
491    pub fn add_relationship(&mut self, methods: Vec<Method>) -> Result<RelationshipId, Error> {
492        if methods.is_empty() {
493            return Err(Error::InvalidMethod { sites: vec![] });
494        }
495
496        for (idx, method) in methods.iter().enumerate() {
497            if method.outputs.is_empty() {
498                return Err(Error::InvalidMethod {
499                    sites: vec![ErrorSite::MethodIndex(idx)],
500                });
501            }
502
503            // declared type counts must match cell-id counts
504            if method.inputs.len() != method.input_types.len()
505                || method.outputs.len() != method.output_types.len()
506            {
507                return Err(Error::InvalidMethod {
508                    sites: vec![ErrorSite::MethodIndex(idx)],
509                });
510            }
511
512            for (&cell_id, &declared) in method.inputs.iter().zip(method.input_types.iter()) {
513                let cell = self.cells.get(cell_id).ok_or(Error::InvalidId)?;
514                if cell.type_id != declared {
515                    return Err(Error::TypeMismatch {
516                        expected: cell.type_id,
517                        found: declared,
518                        sites: vec![ErrorSite::MethodIndex(idx)],
519                    });
520                }
521            }
522
523            for (&cell_id, &declared) in method.outputs.iter().zip(method.output_types.iter()) {
524                let cell = self.cells.get(cell_id).ok_or(Error::InvalidId)?;
525                if cell.kind != CellKind::Cell {
526                    return Err(Error::InvalidCellKind {
527                        sites: vec![ErrorSite::MethodIndex(idx), ErrorSite::Cell(cell_id)],
528                    });
529                }
530                if cell.type_id != declared {
531                    return Err(Error::TypeMismatch {
532                        expected: cell.type_id,
533                        found: declared,
534                        sites: vec![ErrorSite::MethodIndex(idx)],
535                    });
536                }
537            }
538        }
539
540        // Every method in a relationship must reference the same set of cells: the
541        // union of a method's inputs and outputs (as a set) must be identical across
542        // all methods. A relationship models a fixed set of related cells; methods
543        // differ only in which subset of that set they treat as outputs (using the
544        // "ignore an input" pattern), not in which cells they reference at all.
545        let cell_sets: Vec<HashSet<CellId>> = methods
546            .iter()
547            .map(|m| m.inputs.iter().chain(m.outputs.iter()).copied().collect())
548            .collect();
549        if let Some(rel_idx) = cell_sets[1..].iter().position(|set| set != &cell_sets[0]) {
550            let diverging = rel_idx + 1;
551            let mut sites = vec![ErrorSite::MethodIndex(diverging), ErrorSite::MethodIndex(0)];
552            // Symmetric difference of the two cell sets, in a stable order.
553            for &c in cell_sets[diverging].symmetric_difference(&cell_sets[0]) {
554                sites.push(ErrorSite::Cell(c));
555            }
556            return Err(Error::MismatchedMethodCells { sites });
557        }
558
559        // A method's own outputs must be duplicate-free, and no two methods in a
560        // relationship may claim identical or nested output sets: the planner's
561        // matching stage treats a method's pure-output set as an indivisible claim,
562        // so nested claims would make that claim ambiguous while still allowing
563        // overlapping non-nested sets.
564        let mut seen_output_sets: Vec<(usize, HashSet<CellId>, &[CellId])> =
565            Vec::with_capacity(methods.len());
566        for (idx, method) in methods.iter().enumerate() {
567            let output_set: HashSet<CellId> = method.outputs.iter().copied().collect();
568            if output_set.len() != method.outputs.len() {
569                // A cell repeated within this method's own outputs. Report each
570                // distinct repeated cell once, no matter how many times it repeats.
571                let mut sites = vec![ErrorSite::MethodIndex(idx)];
572                let mut seen = HashSet::new();
573                let mut reported = HashSet::new();
574                for &o in &method.outputs {
575                    if !seen.insert(o) && reported.insert(o) {
576                        sites.push(ErrorSite::Cell(o));
577                    }
578                }
579                return Err(Error::InvalidMethodOutputs { sites });
580            }
581            for (earlier, earlier_set, earlier_outputs) in &seen_output_sets {
582                if output_set == *earlier_set
583                    || (output_set.len() < earlier_set.len() && output_set.is_subset(earlier_set))
584                    || (earlier_set.len() < output_set.len() && earlier_set.is_subset(&output_set))
585                {
586                    let mut sites = vec![
587                        ErrorSite::MethodIndex(idx),
588                        ErrorSite::MethodIndex(*earlier),
589                    ];
590                    let offending_outputs: &[CellId] = if output_set.len() <= earlier_set.len() {
591                        &method.outputs
592                    } else {
593                        earlier_outputs
594                    };
595                    for &o in offending_outputs {
596                        sites.push(ErrorSite::Cell(o));
597                    }
598                    return Err(Error::InvalidMethodOutputs { sites });
599                }
600            }
601            seen_output_sets.push((idx, output_set, &method.outputs));
602        }
603
604        // Collect the union of all adjacent cells in insertion order, deduplicated.
605        let mut adj: Vec<CellId> = Vec::new();
606        let mut seen: std::collections::HashSet<CellId> = std::collections::HashSet::new();
607        for method in &methods {
608            for &cell_id in method.inputs.iter().chain(method.outputs.iter()) {
609                if seen.insert(cell_id) {
610                    adj.push(cell_id);
611                }
612            }
613        }
614
615        self.invalidate_prepared_plans();
616        let rel_id = self.relationships.insert(RelationshipData { methods, adj });
617
618        for &cell_id in &self.relationships[rel_id].adj {
619            if let Some(cell) = self.cells.get_mut(cell_id)
620                && !cell.adj.contains(&rel_id)
621            {
622                cell.adj.push(rel_id);
623            }
624        }
625
626        self.guard_independent = false;
627        Ok(rel_id)
628    }
629
630    /// Registers a conditional that activates relationships based on the value of a match
631    /// subject: either a single existing cell, or a [`MatchExpr`] computed from multiple
632    /// input cells.
633    ///
634    /// Each element of `branches` is `(keys, relationships)`: when the match subject's
635    /// value equals any key in `keys`, the branch's `relationships` are added to the active
636    /// set for `propagate`. Branches are evaluated in definition order; first match wins.
637    /// `default` holds relationships activated when no branch matches; pass an empty `Vec`
638    /// for no default.
639    ///
640    /// # Errors
641    ///
642    /// - `Error::InvalidId` — the match subject references a cell not in this sheet.
643    /// - `Error::TypeMismatch` — (expression match subject only) an input cell's registered
644    ///   type doesn't match the expression's declared type for that input.
645    /// - `Error::InvalidConditional` — the match subject's output type does not match `T`;
646    ///   a referenced relationship does not exist; a relationship already appears in
647    ///   another conditional branch; or a branch has no keys.
648    /// - `Error::InvalidCellKind` — a referenced relationship is a fixed `Out` writer.
649    ///
650    /// Guard independence is checked by [`Sheet::validate`] and by [`Sheet::propagate`],
651    /// not by this mutator.
652    ///
653    /// - Complexity: O(I + K + E) time and O(K + R) space for match inputs I,
654    ///   branch keys K, referenced relationships R and their total method-output
655    ///   edges E, plus O(A) when invalidation releases final prepared artifacts of size A.
656    ///
657    /// # Examples
658    /// See the [graph example](crate::sheet).
659    pub fn add_conditional<T: Any + PartialEq + 'static>(
660        &mut self,
661        source: MatchExpr,
662        branches: Vec<(Vec<T>, Vec<RelationshipId>)>,
663        default: Vec<RelationshipId>,
664    ) -> Result<ConditionalId, Error> {
665        match &source.0 {
666            MatchSource::Cell(cell) => {
667                let cell_data = self.cells.get(*cell).ok_or(Error::InvalidId)?;
668                if cell_data.type_id != TypeId::of::<T>() {
669                    return Err(Error::InvalidConditional {
670                        sites: vec![ErrorSite::Cell(*cell)],
671                    });
672                }
673            }
674            MatchSource::Expr(expr) => {
675                if expr.output_type != TypeId::of::<T>() {
676                    return Err(Error::InvalidConditional { sites: vec![] });
677                }
678                for (&cell_id, &declared) in expr.inputs.iter().zip(expr.input_types.iter()) {
679                    let cell_data = self.cells.get(cell_id).ok_or(Error::InvalidId)?;
680                    if cell_data.type_id != declared {
681                        return Err(Error::TypeMismatch {
682                            expected: cell_data.type_id,
683                            found: declared,
684                            sites: vec![],
685                        });
686                    }
687                }
688            }
689        };
690
691        // Collect and validate all relationship IDs (branches + default).
692        let all_rels: Vec<RelationshipId> = branches
693            .iter()
694            .flat_map(|(_, rels)| rels.iter().copied())
695            .chain(default.iter().copied())
696            .collect();
697
698        for &rel_id in &all_rels {
699            let relationship = self
700                .relationships
701                .get(rel_id)
702                .ok_or(Error::InvalidConditional { sites: vec![] })?;
703            if let Some(output) = relationship
704                .methods
705                .iter()
706                .flat_map(|method| &method.outputs)
707                .find(|&&id| self.cells[id].kind == CellKind::Out)
708            {
709                return Err(Error::InvalidCellKind {
710                    sites: vec![ErrorSite::Relationship(rel_id), ErrorSite::Cell(*output)],
711                });
712            }
713            if self.conditional_relationships.contains(&rel_id) {
714                return Err(Error::InvalidConditional {
715                    sites: vec![ErrorSite::Relationship(rel_id)],
716                });
717            }
718        }
719
720        // Validate branch keys are non-empty.
721        for (keys, _) in &branches {
722            if keys.is_empty() {
723                return Err(Error::InvalidConditional { sites: vec![] });
724            }
725        }
726
727        // Check for duplicate relationship IDs within this call.
728        let mut seen: HashSet<RelationshipId> = HashSet::new();
729        for &rel_id in &all_rels {
730            if !seen.insert(rel_id) {
731                return Err(Error::InvalidConditional {
732                    sites: vec![ErrorSite::Relationship(rel_id)],
733                });
734            }
735        }
736
737        // Type-erase branch keys.
738        let typed_branches: Vec<Branch> = branches
739            .into_iter()
740            .map(|(keys, relationships)| Branch {
741                keys: keys
742                    .into_iter()
743                    .map(|k| Box::new(k) as Box<dyn Any>)
744                    .collect(),
745                relationships,
746            })
747            .collect();
748
749        // Record all relationships as conditional so they are excluded from the
750        // unconditional active set in propagate().
751        self.invalidate_prepared_plans();
752        for &rel_id in &all_rels {
753            self.conditional_relationships.insert(rel_id);
754        }
755
756        let id = self.conditionals.insert(ConditionalData {
757            source: source.0,
758            branches: typed_branches,
759            default,
760        });
761
762        self.guard_independent = false;
763        Ok(id)
764    }
765
766    /// Returns `true` if `id` is already claimed as some existing method's output —
767    /// i.e. it cannot legally become an `out` cell's writer target, since that would
768    /// leave two producers claiming the same cell.
769    ///
770    /// - Complexity: O(E) for all registered method-output edges E.
771    fn cell_has_prior_use(&self, id: CellId) -> bool {
772        self.relationships
773            .values()
774            .any(|rel| rel.methods.iter().any(|m| m.outputs.contains(&id)))
775    }
776
777    /// Attaches a requirement to `cell` and returns its stable ID.
778    ///
779    /// `requirement.inputs` may be any cells in the sheet, not only `cell` itself.
780    /// The predicate runs during propagation, so callers can report false results
781    /// separately from propagation failures.
782    ///
783    /// # Errors
784    ///
785    /// - `Error::InvalidId` — `cell`, or one of `requirement`'s input cells, is not a
786    ///   live cell in this sheet.
787    /// - `Error::TypeMismatch` — an input's declared type does not match its cell's
788    ///   registered type.
789    /// - `Error::InvalidRequirement` — the input signature is invalid.
790    ///
791    /// - Complexity: O(k) where k is `requirement`'s input count, plus O(A) when
792    ///   invalidation releases the final owners of prepared artifacts of total size A.
793    ///
794    /// # Examples
795    ///
796    /// ```
797    /// use adam_rs::{Requirement, Sheet};
798    /// let mut sheet = Sheet::new();
799    /// let cell = sheet.add_cell(1_i32);
800    /// let id = sheet.add_requirement(cell, Requirement::from_fn_1(cell, |x: &i32| Ok(*x > 0)))?;
801    /// assert_eq!(sheet.cell_requirements(cell), Some([id].as_slice()));
802    /// # Ok::<(), adam_rs::Error>(())
803    /// ```
804    pub fn add_requirement(
805        &mut self,
806        cell: CellId,
807        requirement: Requirement,
808    ) -> Result<RequirementId, Error> {
809        self.cells.get(cell).ok_or(Error::InvalidId)?;
810        if requirement.inputs.len() != requirement.input_types.len() {
811            return Err(Error::InvalidRequirement);
812        }
813        for (&input_id, &declared) in requirement
814            .inputs
815            .iter()
816            .zip(requirement.input_types.iter())
817        {
818            let input_cell = self.cells.get(input_id).ok_or(Error::InvalidId)?;
819            if input_cell.type_id != declared {
820                return Err(Error::TypeMismatch {
821                    expected: input_cell.type_id,
822                    found: declared,
823                    sites: vec![],
824                });
825            }
826        }
827
828        self.invalidate_prepared_plans();
829        let rid = self.requirements.insert(RequirementData {
830            cell,
831            inputs: requirement.inputs,
832            function: requirement.function,
833        });
834        self.cells[cell].requirements.push(rid);
835        Ok(rid)
836    }
837
838    /// Registers `writer` as the sole producer of its one output cell, which becomes
839    /// an `out` cell: always derived by `writer`, never `write()`-able, but otherwise
840    /// an ordinary, freely-referenceable cell. `requirements` are attached to that
841    /// cell one at a time, in order, via [`Sheet::add_requirement`].
842    ///
843    /// # Errors
844    ///
845    /// - `Error::InvalidOutput` — `writer` does not have exactly one output cell.
846    /// - `Error::InvalidCellKind` — the writer's output cell is already `Source` or
847    ///   `Out` kind, or already claimed as some existing method's output.
848    /// - Any error [`Sheet::add_relationship`] or [`Sheet::add_requirement`] can
849    ///   return.
850    ///
851    /// - Postcondition: not atomic — if `add_requirement` fails partway through
852    ///   `requirements`, the output cell is left `Out`-kind, `writer` is already
853    ///   registered as its producer, and every requirement before the failing one is
854    ///   already attached; none of this is rolled back on error.
855    ///
856    /// - Complexity: O(k + i + m²×c) where k is the number of requirements and i is
857    ///   their total input count, plus the
858    ///   cost of `add_relationship` for `writer` alone (m = 1 method, c = cells in
859    ///   that method), plus O(A) when invalidation releases the final owners of
860    ///   prepared artifacts of total size A.
861    ///
862    /// # Examples
863    ///
864    /// ```
865    /// use adam_rs::{Method, Requirement, Sheet};
866    /// let mut sheet = Sheet::new();
867    /// let input = sheet.add_cell(1_i32);
868    /// let output = sheet.add_cell(0_i32);
869    /// let id = sheet.add_out(
870    ///     Method::from_fn_1_1(input, output, |x: &i32| Ok(*x)),
871    ///     vec![Requirement::from_fn_1(output, |x: &i32| Ok(*x > 0))],
872    /// )?;
873    /// assert_eq!(id, output);
874    /// sheet.propagate()?;
875    /// assert!(sheet.cell_requirements_valid(output));
876    /// # Ok::<(), adam_rs::Error>(())
877    /// ```
878    pub fn add_out(
879        &mut self,
880        writer: Method,
881        requirements: Vec<Requirement>,
882    ) -> Result<CellId, Error> {
883        let out_cell = writer
884            .outputs
885            .first()
886            .copied()
887            .ok_or(Error::InvalidOutput)?;
888        self.add_out_writer(writer)?;
889        for requirement in requirements {
890            self.add_requirement(out_cell, requirement)?;
891        }
892        Ok(out_cell)
893    }
894
895    /// Registers the sole output writer and returns its native relationship identity.
896    ///
897    /// Reports the same writer validation errors as [`Self::add_out`].
898    /// - Complexity: O(r × e + i) for prior relationships r, edges e and writer inputs i.
899    pub(crate) fn add_out_writer(&mut self, writer: Method) -> Result<RelationshipId, Error> {
900        if writer.outputs.len() != 1 {
901            return Err(Error::InvalidOutput);
902        }
903        let out_cell = writer.outputs[0];
904
905        let kind = self.cells.get(out_cell).ok_or(Error::InvalidId)?.kind;
906        if kind != CellKind::Cell || self.cell_has_prior_use(out_cell) {
907            return Err(Error::InvalidCellKind { sites: vec![] });
908        }
909
910        let relationship = self.add_relationship(vec![writer])?;
911        self.cells[out_cell].kind = CellKind::Out;
912        Ok(relationship)
913    }
914
915    /// Attaches `filter` to `cell`.
916    ///
917    /// Never evaluates `filter`'s function — attaching a filter is not a fresh
918    /// external input, so it never changes `cell`'s current effective value. The next
919    /// full [`Sheet::propagate`] call conforms `cell` via the planner's
920    /// `PlanStep::FilterReclamp` step; until then, `read()` reflects whatever `cell`
921    /// held before this call.
922    ///
923    /// # Errors
924    ///
925    /// - `Error::InvalidId` — `cell`, or one of `filter`'s argument cells, is not a
926    ///   live cell in this sheet.
927    /// - `Error::InvalidFilter` — `cell` already has a filter, `filter`'s own value
928    ///   type does not match `cell`'s registered type, or `filter`'s argument list
929    ///   names `cell` itself.
930    /// - `Error::TypeMismatch` — an argument cell's registered type does not match the
931    ///   type `filter` declared for it.
932    ///
933    /// Guard independence is checked by [`Sheet::validate`] and by [`Sheet::propagate`],
934    /// not by this mutator.
935    ///
936    /// - Complexity: O(a) where a is the number of filter argument cells, plus O(A)
937    ///   when invalidation releases the final owners of prepared artifacts of total
938    ///   size A.
939    ///
940    /// # Examples
941    /// See the [graph example](crate::sheet).
942    pub fn add_filter(&mut self, cell: CellId, filter: Filter) -> Result<(), Error> {
943        let cell_type = self.cells.get(cell).ok_or(Error::InvalidId)?.type_id;
944        if self.cells[cell].filter.is_some() {
945            return Err(Error::InvalidFilter);
946        }
947        if filter.0.value_type != cell_type {
948            return Err(Error::InvalidFilter);
949        }
950        if filter.0.args.contains(&cell) {
951            return Err(Error::InvalidFilter);
952        }
953        for (&arg_id, &declared) in filter.0.args.iter().zip(filter.0.arg_types.iter()) {
954            let arg_cell = self.cells.get(arg_id).ok_or(Error::InvalidId)?;
955            if arg_cell.type_id != declared {
956                return Err(Error::TypeMismatch {
957                    expected: arg_cell.type_id,
958                    found: declared,
959                    sites: vec![],
960                });
961            }
962        }
963
964        self.invalidate_prepared_plans();
965        for &arg in &filter.0.args {
966            self.filter_dependents.entry(arg).or_default().push(cell);
967        }
968        self.cells[cell].filter = Some(filter.0);
969        self.guard_independent = false;
970        Ok(())
971    }
972
973    /// Returns the argument cells of `id`'s filter, in declaration order.
974    ///
975    /// Returns `None` if `id` is not a live cell in this sheet, or has no filter.
976    ///
977    /// # Examples
978    /// See the [graph example](crate::sheet).
979    pub fn filter_args(&self, id: CellId) -> Option<&[CellId]> {
980        self.cells
981            .get(id)?
982            .filter
983            .as_ref()
984            .map(|f| f.args.as_slice())
985    }
986
987    /// Returns the live cells whose filter references `id` as one of its dynamic
988    /// arguments — the reverse of a filter's own argument list ([`Sheet::filter_args`]).
989    ///
990    /// - Postcondition: empty if no live cell's filter references `id`.
991    ///
992    /// # Examples
993    /// See the [graph example](crate::sheet).
994    pub fn filter_dependents(&self, id: CellId) -> &[CellId] {
995        self.filter_dependents
996            .get(&id)
997            .map(Vec::as_slice)
998            .unwrap_or(&[])
999    }
1000
1001    /// Returns the kind of validation/derivation `id`'s filter performs, if it has one.
1002    ///
1003    /// Returns `None` if `id` is not a live cell in this sheet, or has no filter.
1004    ///
1005    /// # Examples
1006    /// See the [graph example](crate::sheet).
1007    pub fn filter_kind(&self, id: CellId) -> Option<&FilterKind> {
1008        self.cells.get(id)?.filter.as_ref().map(|f| &f.kind)
1009    }
1010
1011    /// Returns `id`'s filter's current `(lo, hi)` bounds, if it has a [`FilterKind::Range`]
1012    /// filter.
1013    ///
1014    /// Resolves the filter's argument cells' current effective values via the same path
1015    /// [`Sheet::add_filter`] already uses, then calls the filter's `bounds` function.
1016    ///
1017    /// Returns `None` if `id` is not a live cell in this sheet, has no filter, its filter's
1018    /// kind isn't [`FilterKind::Range`], its bounds have a different type from `T`,
1019    /// or the range expression fails to evaluate against
1020    /// the filter's current argument values (e.g. a fallible arithmetic op in a range
1021    /// endpoint) — the same degraded-to-`None` outcome as any other reason no live bounds
1022    /// are available right now.
1023    ///
1024    /// - Complexity: O(a) where a is the number of the filter's argument cells.
1025    ///
1026    /// # Examples
1027    /// See the [graph example](crate::sheet).
1028    pub fn filter_range<T: Any>(&self, id: CellId) -> Option<(T, T)> {
1029        let filter = self.cells.get(id)?.filter.as_ref()?;
1030        let FilterKind::Range { bounds } = &filter.kind else {
1031            return None;
1032        };
1033        let args: Vec<&dyn Any> = filter
1034            .args
1035            .iter()
1036            .map(|&a| self.cells[a].effective())
1037            .collect();
1038        let (lo, hi) = bounds(&args)?;
1039        Some((*lo.downcast::<T>().ok()?, *hi.downcast::<T>().ok()?))
1040    }
1041
1042    /// Returns the filter violation recorded for `id` as of the last full
1043    /// `propagate()` call, if any.
1044    ///
1045    /// - Postcondition: `None` if `id` has no filter, `id`'s filter's last-checked
1046    ///   value held, or no full `propagate()` has run since `id` was last a plain
1047    ///   external write.
1048    ///
1049    /// # Examples
1050    /// See the [graph example](crate::sheet).
1051    pub fn filter_violation(&self, id: CellId) -> Option<&FilterViolation> {
1052        self.last_filter_violations.get(&id)
1053    }
1054
1055    /// Iterates cells whose filter is currently violated, as of the last full
1056    /// `propagate()` call.
1057    ///
1058    /// - Complexity: O(n) where n is the number of currently-violated filters.
1059    ///
1060    /// # Examples
1061    /// See the [graph example](crate::sheet).
1062    pub fn filter_violated_cells(&self) -> impl Iterator<Item = CellId> + '_ {
1063        self.last_filter_violations.keys().copied()
1064    }
1065
1066    /// Returns the set of root cells currently determining a violated filter's own
1067    /// value or any of its argument values, as of the last full `propagate()` call —
1068    /// the same "which upstream cells caused this" query
1069    /// `requirement_contributing_cells`/`requirement_violation_cells` already provide
1070    /// for `Requirement`.
1071    ///
1072    /// - Postcondition: empty if no filter is currently violated.
1073    /// - Complexity: O(sum of `contributing_cells` cost over every violated filter and
1074    ///   its argument cells).
1075    ///
1076    /// # Examples
1077    /// See the [graph example](crate::sheet).
1078    pub fn filter_violation_cells(&self) -> HashSet<CellId> {
1079        let mut result = HashSet::new();
1080        for cell_id in self.filter_violated_cells() {
1081            result.extend(self.contributing_cells(cell_id));
1082            if let Some(args) = self.filter_args(cell_id) {
1083                for &arg in args {
1084                    result.extend(self.contributing_cells(arg));
1085                }
1086            }
1087        }
1088        result
1089    }
1090
1091    /// Returns the requirements attached to `id`, in attachment order.
1092    ///
1093    /// Returns `None` if `id` is not a live cell in this sheet.
1094    ///
1095    /// # Examples
1096    /// See the [graph example](crate::sheet).
1097    pub fn cell_requirements(&self, id: CellId) -> Option<&[RequirementId]> {
1098        self.cells.get(id).map(|c| c.requirements.as_slice())
1099    }
1100
1101    /// Returns the cell requirement `id` is attached to.
1102    ///
1103    /// Returns `None` if `id` is not a live requirement in this sheet.
1104    ///
1105    /// # Examples
1106    /// See the [graph example](crate::sheet).
1107    pub fn requirement_cell(&self, id: RequirementId) -> Option<CellId> {
1108        self.requirements.get(id).map(|c| c.cell)
1109    }
1110
1111    /// Returns the cells requirement `id` reads.
1112    ///
1113    /// Returns `None` if `id` is not a live requirement in this sheet.
1114    ///
1115    /// # Examples
1116    /// See the [graph example](crate::sheet).
1117    pub fn requirement_inputs(&self, id: RequirementId) -> Option<&[CellId]> {
1118        self.requirements.get(id).map(|c| c.inputs.as_slice())
1119    }
1120
1121    /// Returns `true` if every requirement on `id` held as of the last `propagate()`
1122    /// call.
1123    ///
1124    /// Returns `false` if no propagation has run yet. Also returns `true` for an
1125    /// `id` that is not a live cell in this sheet, since no requirement can have
1126    /// failed for a cell that doesn't exist.
1127    ///
1128    /// # Examples
1129    /// See the [graph example](crate::sheet).
1130    pub fn cell_requirements_valid(&self, id: CellId) -> bool {
1131        if self.last_plan.is_none() {
1132            return false;
1133        }
1134        !self.last_requirement_violations.contains_key(&id)
1135    }
1136
1137    /// Iterates the requirements on `id` that evaluated to `false` as of the last
1138    /// `propagate()` call.
1139    ///
1140    /// - Postcondition: empty if `id`'s requirements all held, `id` is not a live
1141    ///   cell in this sheet, or no propagation has run yet.
1142    ///
1143    /// # Examples
1144    /// See the [graph example](crate::sheet).
1145    pub fn violated_requirements(&self, id: CellId) -> impl Iterator<Item = RequirementId> + '_ {
1146        self.last_requirement_violations
1147            .get(&id)
1148            .into_iter()
1149            .flatten()
1150            .copied()
1151    }
1152
1153    /// Returns the set of root cells that could determine `id`'s value for *some* choice of
1154    /// cell strengths, as of the last `propagate()` call.
1155    ///
1156    /// A cell is a root candidate for `current` whenever some *active* relationship
1157    /// adjacent to `current` has a method producing it — not just the one method the
1158    /// current strengths happen to have selected: a different strength ordering could pick
1159    /// a different method of that same relationship, making any of its other cells the
1160    /// source instead. So every method (of every active relationship touching `current`)
1161    /// with `current` among its outputs contributes its inputs to the walk, not only the
1162    /// currently-selected one. A cell outside [`Sheet::is_forced`]'s pure-output
1163    /// classification contributes its own stored aspiration. This includes a
1164    /// self-referencing input even when a mandatory writer prevents the cell from
1165    /// remaining unclaimed: its seed can still depend on that aspiration.
1166    ///
1167    /// Every visited cell is also checked against every conditional in the sheet: if any of
1168    /// that conditional's branches (or its default) has a method whose outputs include the
1169    /// visited cell, the conditional's match cell is added as a contributor too, and is
1170    /// itself traced recursively. This covers both an active producer that happens to
1171    /// belong to a conditional branch, and a cell with *no* active producer specifically
1172    /// because the branch that would define it isn't the one currently selected (that
1173    /// absence is itself a fact controlled by the match cell, not an indication that the
1174    /// match cell is irrelevant).
1175    ///
1176    /// A cell that can remain a source also contributes its dynamic filter arguments:
1177    /// the filter determines its effective value. Filters on outputs claimed in every
1178    /// acyclic assignment, including self-referencing outputs, only diagnose values
1179    /// and do not add value dependencies.
1180    ///
1181    /// Eligibility searches use the candidate's complete input/output relationship
1182    /// component within the last successful active set. That completed assignment
1183    /// witnesses feasibility of the independent components. Registered methods are
1184    /// never replaced or removed, so writes, appended structure and failed
1185    /// propagation preserve this witness; newly registered relationships stay outside
1186    /// these assignment searches until successful propagation publishes another
1187    /// active set. Filter arguments and potential conditional contributors still
1188    /// come from the current registered graph.
1189    ///
1190    /// - Postcondition: returns `{id}` if no propagation has run yet, or if `id` is not
1191    ///   forced and no active or conditional relationship or dynamic filter could produce it.
1192    ///
1193    /// - Complexity: O(N · (R·M·K² + B·M·K)) where N is the number of cells reachable from `id`
1194    ///   (including conditional match cells), R is the number of relationships adjacent to
1195    ///   a visited cell, M is the maximum number of methods per relationship, K is
1196    ///   the maximum input/output count per method, and B is the
1197    ///   total number of branch/default relationships across all conditionals, plus the
1198    ///   number of traversed filter argument edges. Each visited non-forced filtered
1199    ///   cell with dynamic arguments that is not currently a source adds at most one
1200    ///   exact eligibility search after expected linear traversal of that candidate's
1201    ///   component adjacency (including inactive adjacency entries). Each search takes
1202    ///   expected O(T + U · (1 + A · K²)) time, where T is relationship storage slots
1203    ///   scanned, A is active relationships in that component, and U counts that
1204    ///   search's recursive calls and examined method candidates. U is bounded by
1205    ///   O(1 + M + … + M^A), including partial checks when M = 1; disconnected
1206    ///   components do not multiply its search tree. Debug builds add O(G) validation
1207    ///   during each component discovery, where G is prepared active relationships.
1208    ///   Traversal storage is O(N + E) for visited cells and queued dependency edges E,
1209    ///   plus visited component cells/relationships and O(1 + A · (1 + K)) auxiliary
1210    ///   storage for one eligibility search. Repeated queries do not cache searches.
1211    ///
1212    /// # Examples
1213    /// See the [graph example](crate::sheet).
1214    pub fn contributing_cells(&self, id: CellId) -> HashSet<CellId> {
1215        if self.last_plan.is_none() {
1216            return HashSet::from([id]);
1217        }
1218        let mut result = HashSet::new();
1219        let mut visited: HashSet<CellId> = HashSet::new();
1220        let mut stack = vec![id];
1221        while let Some(current) = stack.pop() {
1222            if !visited.insert(current) {
1223                continue;
1224            }
1225
1226            for match_cell in self.conditionals_potentially_producing(current) {
1227                stack.push(match_cell);
1228            }
1229
1230            if !self.is_forced(current) {
1231                result.insert(current);
1232                if let Some(prepared) = &self.last_plan
1233                    && let Some(args) = self.filter_args(current)
1234                    && !args.is_empty()
1235                    && (self.is_source(current) || {
1236                        let component = crate::planner::active_relationship_component(
1237                            &self.cells,
1238                            &self.relationships,
1239                            &prepared.active,
1240                            current,
1241                        );
1242                        crate::planner::can_be_source(&self.relationships, &component, current)
1243                    })
1244                {
1245                    stack.extend(args.iter().copied());
1246                }
1247            }
1248
1249            let Some(cell) = self.cells.get(current) else {
1250                continue;
1251            };
1252            for &rel_id in &cell.adj {
1253                if !self.is_relationship_active(rel_id) {
1254                    continue;
1255                }
1256                for method in &self.relationships[rel_id].methods {
1257                    if !method.outputs.contains(&current) {
1258                        continue;
1259                    }
1260                    for &input in &method.inputs {
1261                        if method.outputs.contains(&input) {
1262                            result.insert(input);
1263                        } else {
1264                            stack.push(input);
1265                        }
1266                    }
1267                }
1268            }
1269        }
1270        result
1271    }
1272
1273    /// Returns `true` if `rel_id` was part of the active relationship set for the last
1274    /// successful `propagate()` call — an unconditional relationship is always active; a conditional
1275    /// branch/default relationship is active only while its conditional currently selects
1276    /// it.
1277    ///
1278    /// Returns `false` if no propagation has run yet.
1279    fn is_relationship_active(&self, rel_id: RelationshipId) -> bool {
1280        self.last_plan
1281            .as_ref()
1282            .is_some_and(|plan| plan.active.contains(&rel_id))
1283    }
1284
1285    /// Returns the match cells of every conditional with at least one branch (or default)
1286    /// relationship that touches `cell` (as an input or output of any of its methods) —
1287    /// every conditional whose branch choice currently determines, or could determine,
1288    /// `cell`'s value or whether it has an active producer at all.
1289    ///
1290    /// - Complexity: O(B) where B is the total number of branch/default relationships
1291    ///   across all conditionals.
1292    fn conditionals_potentially_producing(&self, cell: CellId) -> Vec<CellId> {
1293        self.conditionals
1294            .values()
1295            .filter(|cond| {
1296                cond.branches
1297                    .iter()
1298                    .flat_map(|branch| branch.relationships.iter())
1299                    .chain(cond.default.iter())
1300                    .any(|&rel_id| self.relationships[rel_id].adj.contains(&cell))
1301            })
1302            .flat_map(|cond| cond.match_cells().iter().copied())
1303            .collect()
1304    }
1305
1306    /// Returns the union of [`Sheet::contributing_cells`] over requirement `id`'s own
1307    /// declared inputs.
1308    ///
1309    /// Returns an empty set if `id` is not a live requirement in this sheet.
1310    ///
1311    /// - Complexity: The sum of [`Self::contributing_cells`] costs over the requirement's
1312    ///   inputs, with O(N) space for their union of N root cells.
1313    ///
1314    /// # Examples
1315    /// See the [graph example](crate::sheet).
1316    pub fn requirement_contributing_cells(&self, id: RequirementId) -> HashSet<CellId> {
1317        let Some(requirement) = self.requirements.get(id) else {
1318            return HashSet::new();
1319        };
1320        requirement
1321            .inputs
1322            .iter()
1323            .flat_map(|&input| self.contributing_cells(input))
1324            .collect()
1325    }
1326
1327    /// Returns the union of `contributing_cells` over every cell with at least one
1328    /// requirement — the set of cells currently determining at least one
1329    /// requirement-checked value, as of the last `propagate()` call.
1330    ///
1331    /// - Postcondition: empty if no cell in the sheet has any requirements.
1332    /// - Complexity: O(sum of `contributing_cells` cost over every cell with
1333    ///   requirements).
1334    ///
1335    /// # Examples
1336    /// See the [graph example](crate::sheet).
1337    pub fn requirement_relevant_cells(&self) -> HashSet<CellId> {
1338        self.cells
1339            .iter()
1340            .filter(|(_, c)| !c.requirements.is_empty())
1341            .flat_map(|(id, _)| self.contributing_cells(id))
1342            .collect()
1343    }
1344
1345    /// Returns the union of `requirement_contributing_cells` over every requirement
1346    /// that evaluated `false` as of the last `propagate()` call, across every cell
1347    /// in the sheet.
1348    ///
1349    /// - Postcondition: empty if no requirement anywhere in the sheet currently
1350    ///   fails.
1351    /// - Complexity: O(sum of `requirement_contributing_cells` cost over every
1352    ///   violated requirement).
1353    ///
1354    /// # Examples
1355    /// See the [graph example](crate::sheet).
1356    pub fn requirement_violation_cells(&self) -> HashSet<CellId> {
1357        self.cells
1358            .keys()
1359            .flat_map(|id| self.violated_requirements(id))
1360            .flat_map(|rid| self.requirement_contributing_cells(rid))
1361            .collect()
1362    }
1363
1364    /// Writes a value to a cell, incrementing the cell's write-recency strength.
1365    ///
1366    /// Each successful `write` increments a global monotonic counter and assigns
1367    /// the new value to `cell.strength`, so the most-recently-written cell always
1368    /// has the highest strength.
1369    ///
1370    /// - Postcondition: any pending derived override is cleared, so the written value is immediately visible via `read()`.
1371    /// - Complexity: Expected O(1) validation and phase-membership work, plus O(A)
1372    ///   when a claimed-cell write releases the final owners of prepared artifacts
1373    ///   of total size A, excluding destruction of replaced values.
1374    ///
1375    /// # Errors
1376    ///
1377    /// - `Error::InvalidId` — `id` is not a cell in this sheet.
1378    /// - `Error::TypeMismatch` — `T` does not match the cell's registered `TypeId`.
1379    /// - `Error::InvalidCellKind` — `id` is `Out`-kind.
1380    ///
1381    /// # Examples
1382    /// See the [graph example](crate::sheet).
1383    pub fn write<T: Any + 'static>(&mut self, id: CellId, value: T) -> Result<(), Error> {
1384        if self.cells.get(id).is_some_and(|c| c.kind == CellKind::Out) {
1385            return Err(Error::InvalidCellKind { sites: vec![] });
1386        }
1387        let cell_type = self.cells.get(id).ok_or(Error::InvalidId)?.type_id;
1388        if cell_type != TypeId::of::<T>() {
1389            return Err(Error::TypeMismatch {
1390                expected: cell_type,
1391                found: TypeId::of::<T>(),
1392                sites: vec![],
1393            });
1394        }
1395
1396        self.invalidate_after_write(id);
1397        self.last_filter_violations.remove(&id);
1398        self.next_strength += 1;
1399        let cell = &mut self.cells[id];
1400        cell.strength = self.next_strength | (1u64 << 63);
1401        if cell.remembered_strength.is_some() {
1402            cell.remembered_strength = Some(cell.strength);
1403        }
1404        cell.source = Box::new(value);
1405        cell.derived = None;
1406        Ok(())
1407    }
1408
1409    /// Returns a shared reference to the cell's effective current value: its derived
1410    /// override if one exists, otherwise its source (last written) value.
1411    ///
1412    /// # Errors
1413    ///
1414    /// - `Error::InvalidId` — `id` is not a cell in this sheet.
1415    /// - `Error::TypeMismatch` — `T` does not match the cell's registered `TypeId`.
1416    ///
1417    /// # Examples
1418    /// See the [graph example](crate::sheet).
1419    pub fn read<T: Any + 'static>(&self, id: CellId) -> Result<&T, Error> {
1420        let cell = self.cells.get(id).ok_or(Error::InvalidId)?;
1421        if cell.type_id != TypeId::of::<T>() {
1422            return Err(Error::TypeMismatch {
1423                expected: cell.type_id,
1424                found: TypeId::of::<T>(),
1425                sites: vec![],
1426            });
1427        }
1428        Ok(cell
1429            .effective()
1430            .downcast_ref::<T>()
1431            .expect("type checked above"))
1432    }
1433
1434    /// Returns the raw `source` slot: the last value written via `write()`/`add_cell`,
1435    /// ignoring any `derived` override produced by a self-referencing method or a
1436    /// conditionally forced relationship. For an ordinary (unshadowed) derived cell,
1437    /// `propagate()` writes straight into this same slot, so `source()` agrees with
1438    /// `read()`; the two diverge only for cells currently shadowed.
1439    ///
1440    /// # Errors
1441    ///
1442    /// - `Error::InvalidId` — `id` is not a cell in this sheet.
1443    /// - `Error::TypeMismatch` — `T` does not match the cell's registered `TypeId`.
1444    ///
1445    /// # Examples
1446    ///
1447    /// ```rust
1448    /// use adam_rs::Sheet;
1449    ///
1450    /// let mut sheet = Sheet::new();
1451    /// let a = sheet.add_cell(3_i32);
1452    /// sheet.write(a, 8_i32).unwrap();
1453    /// assert_eq!(*sheet.source::<i32>(a).unwrap(), 8);
1454    /// ```
1455    pub fn source<T: Any + 'static>(&self, id: CellId) -> Result<&T, Error> {
1456        let cell = self.cells.get(id).ok_or(Error::InvalidId)?;
1457        if cell.type_id != TypeId::of::<T>() {
1458            return Err(Error::TypeMismatch {
1459                expected: cell.type_id,
1460                found: TypeId::of::<T>(),
1461                sites: vec![],
1462            });
1463        }
1464        Ok(cell.source.downcast_ref::<T>().expect("type checked above"))
1465    }
1466
1467    /// Iterates over cells updated during the most recent successful `propagate()` call.
1468    ///
1469    /// This includes cells written by selected methods and cells that reverted to their
1470    /// source values because the relationship that had been shadowing them (self-referencing
1471    /// or conditionally forced) is no longer producing them this round (Phase 5), even though
1472    /// no method wrote to them this round. It does not attempt to compare old/new values for
1473    /// equality.
1474    ///
1475    /// - Complexity: O(n) where n is the number of changed cells.
1476    ///
1477    /// # Examples
1478    /// See the [graph example](crate::sheet).
1479    pub fn changed(&self) -> impl Iterator<Item = CellId> + '_ {
1480        self.changed_cells.iter().copied()
1481    }
1482
1483    /// Clears the changed-cell set and resets each cell's `changed` flag.
1484    ///
1485    /// Call after processing the results of `propagate()`.
1486    ///
1487    /// - Complexity: O(n) where n is the number of changed cells.
1488    ///
1489    /// # Examples
1490    /// See the [graph example](crate::sheet).
1491    pub fn clear_changed(&mut self) {
1492        for id in std::mem::take(&mut self.changed_cells) {
1493            if let Some(cell) = self.cells.get_mut(id) {
1494                cell.changed = false;
1495            }
1496        }
1497    }
1498
1499    /// Validates static guard independence for the current sheet structure.
1500    ///
1501    /// A filter's argument cells and a conditional's match cells must not depend on any
1502    /// cell governed by that filter or conditional in the static dependency graph. A
1503    /// successful call records the current structure as validated.
1504    ///
1505    /// # Errors
1506    ///
1507    /// - `Error::DependencyCycle` — a guard edge lies on a cycle in the static
1508    ///   dependency graph.
1509    ///
1510    /// # Examples
1511    ///
1512    /// ```rust
1513    /// use adam_rs::{Filter, Method, Sheet};
1514    ///
1515    /// let mut sheet = Sheet::new();
1516    /// let limit = sheet.add_cell(10_i32);
1517    /// let value = sheet.add_cell(5_i32);
1518    /// let copy = sheet.add_cell(0_i32);
1519    /// sheet
1520    ///     .add_relationship(vec![Method::from_fn_1_1(value, copy, |x: &i32| Ok(*x))])
1521    ///     .unwrap();
1522    /// sheet
1523    ///     .add_filter(value, Filter::from_fn_1(limit, |v: &i32, hi: &i32| Ok((*v).min(*hi))))
1524    ///     .unwrap();
1525    /// sheet.validate().unwrap();
1526    /// ```
1527    ///
1528    /// - Complexity: O(1) when no structural mutation has happened since the last
1529    ///   successful validation; otherwise O(V + E), where V is the number of cells and
1530    ///   E is the number of static method and guard edges.
1531    pub fn validate(&mut self) -> Result<(), Error> {
1532        if self.guard_independent {
1533            return Ok(());
1534        }
1535        if let Some(path) = self.guard_violation() {
1536            return Err(Error::DependencyCycle {
1537                sites: path.into_sites(),
1538            });
1539        }
1540        self.guard_independent = true;
1541        Ok(())
1542    }
1543
1544    /// Iterates all live cell IDs in the sheet.
1545    ///
1546    /// - Complexity: O(n) where n is the number of cells.
1547    ///
1548    /// # Examples
1549    /// See the [graph example](crate::sheet).
1550    pub fn cells(&self) -> impl Iterator<Item = CellId> + '_ {
1551        self.cells.keys()
1552    }
1553
1554    /// Iterates all live relationship IDs in the sheet.
1555    ///
1556    /// - Complexity: O(n) where n is the number of relationships.
1557    ///
1558    /// # Examples
1559    /// See the [graph example](crate::sheet).
1560    pub fn relationships(&self) -> impl Iterator<Item = RelationshipId> + '_ {
1561        self.relationships.keys()
1562    }
1563
1564    /// Returns the relationships adjacent to `id`.
1565    ///
1566    /// Returns `None` if `id` is not a live cell in this sheet.
1567    ///
1568    /// - Complexity: O(1).
1569    ///
1570    /// # Examples
1571    /// See the [graph example](crate::sheet).
1572    pub fn cell_adj(&self, id: CellId) -> Option<&[RelationshipId]> {
1573        self.cells.get(id).map(|c| c.adj.as_slice())
1574    }
1575
1576    /// Returns the cells adjacent to `id` (union across all methods).
1577    ///
1578    /// Returns `None` if `id` is not a live relationship in this sheet.
1579    ///
1580    /// - Complexity: O(1).
1581    ///
1582    /// # Examples
1583    /// See the [graph example](crate::sheet).
1584    pub fn relationship_adj(&self, id: RelationshipId) -> Option<&[CellId]> {
1585        self.relationships.get(id).map(|r| r.adj.as_slice())
1586    }
1587
1588    /// Evaluates conditional `cond`'s current live match value.
1589    ///
1590    /// # Errors
1591    ///
1592    /// - `Error::MethodFailed` — the match subject is a [`MatchExpr`] whose function
1593    ///   returned an error; its site identifies the executing conditional.
1594    ///
1595    /// - Complexity: O(I) time and space for expression inputs I, plus callback cost.
1596    fn evaluate_match_source(
1597        &self,
1598        id: ConditionalId,
1599        cond: &ConditionalData,
1600    ) -> Result<MatchValue<'_>, Error> {
1601        match &cond.source {
1602            MatchSource::Cell(id) => Ok(MatchValue::Ref(self.cells[*id].effective())),
1603            MatchSource::Expr(expr) => {
1604                let args: Vec<&dyn Any> = expr
1605                    .inputs
1606                    .iter()
1607                    .map(|&id| self.cells[id].effective())
1608                    .collect();
1609                let value = (expr.function)(&args).map_err(|error| Error::MethodFailed {
1610                    error,
1611                    sites: vec![ErrorSite::Conditional(id)],
1612                })?;
1613                Ok(MatchValue::Owned(value))
1614            }
1615        }
1616    }
1617
1618    /// Evaluates conditional `cond` against staged propagation state.
1619    ///
1620    /// Uses `stage`'s writes first, then falls back to each cell's `source` value,
1621    /// matching the state visible after Phase 0 excludes old derived overrides and any
1622    /// pre-plan steps already staged.
1623    ///
1624    /// # Errors
1625    ///
1626    /// - `Error::MethodFailed` — the match subject is a [`MatchExpr`] whose function
1627    ///   returned an error; its site identifies the executing conditional.
1628    ///
1629    /// - Complexity: O(I) time and space for expression inputs I, plus callback cost.
1630    fn evaluate_match_source_staged<'a>(
1631        &'a self,
1632        id: ConditionalId,
1633        cond: &ConditionalData,
1634        stage: &'a PropagationStage,
1635    ) -> Result<MatchValue<'a>, Error> {
1636        match &cond.source {
1637            MatchSource::Cell(id) => Ok(MatchValue::Ref(stage.effective(&self.cells, *id))),
1638            MatchSource::Expr(expr) => {
1639                let args: Vec<&dyn Any> = expr
1640                    .inputs
1641                    .iter()
1642                    .map(|&id| stage.effective(&self.cells, id))
1643                    .collect();
1644                let value = (expr.function)(&args).map_err(|error| Error::MethodFailed {
1645                    error,
1646                    sites: vec![ErrorSite::Conditional(id)],
1647                })?;
1648                Ok(MatchValue::Owned(value))
1649            }
1650        }
1651    }
1652
1653    /// Returns the equality function used to compare `cond`'s match value against branch
1654    /// keys: the match cell's own `eq_fn` for a plain match subject, or the expression's
1655    /// captured `eq_fn` for a computed one.
1656    fn match_eq_fn(&self, cond: &ConditionalData) -> fn(&dyn Any, &dyn Any) -> bool {
1657        match &cond.source {
1658            MatchSource::Cell(id) => self.cells[*id].eq_fn,
1659            MatchSource::Expr(expr) => expr.eq_fn,
1660        }
1661    }
1662
1663    /// Builds the active relationship set against staged propagation state.
1664    ///
1665    /// Every conditional reads `stage`'s writes first and otherwise falls back to cell
1666    /// `source` values rather than any live
1667    /// `derived` override already present on the sheet.
1668    ///
1669    /// # Errors
1670    ///
1671    /// - `Error::MethodFailed` — an expression-sourced conditional's function returned an
1672    ///   error.
1673    fn build_active_set_staged(
1674        &self,
1675        stage: &PropagationStage,
1676    ) -> Result<HashSet<RelationshipId>, Error> {
1677        let mut active: HashSet<RelationshipId> = self
1678            .relationships
1679            .keys()
1680            .filter(|id| !self.conditional_relationships.contains(id))
1681            .collect();
1682
1683        for (id, cond) in &self.conditionals {
1684            let value = self.evaluate_match_source_staged(id, cond, stage)?;
1685            let value_ref = value.as_dyn();
1686            let eq_fn = self.match_eq_fn(cond);
1687
1688            let mut matched = false;
1689            for branch in &cond.branches {
1690                if branch.keys.iter().any(|key| eq_fn(value_ref, key.as_ref())) {
1691                    for &rel_id in &branch.relationships {
1692                        active.insert(rel_id);
1693                    }
1694                    matched = true;
1695                    break;
1696                }
1697            }
1698            if !matched {
1699                for &rel_id in &cond.default {
1700                    active.insert(rel_id);
1701                }
1702            }
1703        }
1704
1705        Ok(active)
1706    }
1707
1708    /// Records the actual dependency producers and write classifications for `plan`.
1709    ///
1710    /// - Complexity: expected O(V + E), where V is the number of selected steps and E is
1711    ///   the number of method inputs, outputs, and filter arguments.
1712    fn plan_provenance(&self, plan: &Plan) -> PlanProvenance {
1713        let mut provenance = PlanProvenance::default();
1714        let mut effective_producers = HashMap::new();
1715        let mut source_producers = HashMap::new();
1716
1717        for &step in &plan.execution_order {
1718            let step_provenance = match step {
1719                PlanStep::Method(rel_id, method_index) => {
1720                    let method = &self.relationships[rel_id].methods[method_index];
1721                    let method_inputs: HashSet<CellId> = method.inputs.iter().copied().collect();
1722                    let method_outputs: HashSet<CellId> = method.outputs.iter().copied().collect();
1723                    let mut input_producers = Vec::with_capacity(method.inputs.len());
1724                    let mut seed_inputs = Vec::new();
1725                    for &input in &method.inputs {
1726                        if method_outputs.contains(&input) {
1727                            seed_inputs.push(input);
1728                            input_producers.push(None);
1729                        } else {
1730                            input_producers.push(effective_producers.get(&input).copied());
1731                        }
1732                    }
1733                    let output_classes: Vec<_> = method
1734                        .outputs
1735                        .iter()
1736                        .map(|&output| {
1737                            (
1738                                output,
1739                                if method_inputs.contains(&output)
1740                                    || plan.forced_outputs.contains(&output)
1741                                {
1742                                    OutputClassification::Derived
1743                                } else {
1744                                    OutputClassification::Source
1745                                },
1746                            )
1747                        })
1748                        .collect();
1749                    for &(output, classification) in &output_classes {
1750                        effective_producers.insert(output, step);
1751                        if classification == OutputClassification::Source {
1752                            source_producers.insert(output, step);
1753                        }
1754                    }
1755                    StepProvenance {
1756                        input_producers,
1757                        seed_inputs,
1758                        source_producer: None,
1759                        output_classes,
1760                    }
1761                }
1762                PlanStep::FilterReclamp(cell) => {
1763                    let filter = self.cells[cell]
1764                        .filter
1765                        .as_ref()
1766                        .expect("plan() only emits FilterReclamp for a filtered cell");
1767                    let input_producers = filter
1768                        .args
1769                        .iter()
1770                        .map(|input| effective_producers.get(input).copied())
1771                        .collect();
1772                    let step_provenance = StepProvenance {
1773                        input_producers,
1774                        seed_inputs: Vec::new(),
1775                        source_producer: source_producers.get(&cell).copied(),
1776                        output_classes: vec![(cell, OutputClassification::Derived)],
1777                    };
1778                    effective_producers.insert(cell, step);
1779                    step_provenance
1780                }
1781            };
1782            provenance.steps.insert(step, step_provenance);
1783        }
1784        provenance.output_producers = effective_producers;
1785        provenance
1786    }
1787
1788    /// Checks that a staged prerequisite step has identical selected producer paths in both plans.
1789    ///
1790    /// - Postcondition: `true` implies the selected method/filter, its input producers,
1791    ///   seed inputs, source producer, and output classifications all match.
1792    /// - Complexity: O(I + O) for the step's input and output provenance lengths.
1793    fn prerequisite_step_is_compatible(
1794        &self,
1795        pre_plan: &PlanProvenance,
1796        final_plan: &PlanProvenance,
1797        step: PlanStep,
1798        stage: &PropagationStage,
1799    ) -> bool {
1800        let Some(staged) = stage.step_provenance.get(&step) else {
1801            return false;
1802        };
1803        pre_plan.steps.get(&step) == Some(staged) && final_plan.steps.get(&step) == Some(staged)
1804    }
1805
1806    /// Returns concrete cells and relationships implicated by a staged-step mismatch.
1807    ///
1808    /// - Complexity: O(K) sites for the step's cells and producer edges.
1809    fn conflict_sites_for_step(
1810        &self,
1811        step: PlanStep,
1812        pre_plan: &PlanProvenance,
1813        final_plan: &PlanProvenance,
1814    ) -> Vec<ErrorSite> {
1815        let mut sites = Vec::new();
1816        let mut seen_sites = HashSet::new();
1817        let mut add_site = |site| {
1818            if seen_sites.insert(site) {
1819                sites.push(site);
1820            }
1821        };
1822        let mut cells = Vec::new();
1823
1824        match step {
1825            PlanStep::Method(rel_id, method_index) => {
1826                add_site(ErrorSite::Relationship(rel_id));
1827                add_site(ErrorSite::Method(rel_id, method_index));
1828                let method = &self.relationships[rel_id].methods[method_index];
1829                cells.extend(method.inputs.iter().copied());
1830                cells.extend(method.outputs.iter().copied());
1831            }
1832            PlanStep::FilterReclamp(cell) => {
1833                cells.push(cell);
1834                if let Some(filter) = &self.cells[cell].filter {
1835                    cells.extend(filter.args.iter().copied());
1836                }
1837            }
1838        }
1839
1840        for provenance in [pre_plan.steps.get(&step), final_plan.steps.get(&step)]
1841            .into_iter()
1842            .flatten()
1843        {
1844            for producer in provenance
1845                .input_producers
1846                .iter()
1847                .copied()
1848                .flatten()
1849                .chain(provenance.source_producer)
1850            {
1851                Self::add_step_relationship_site(producer, &mut add_site);
1852            }
1853        }
1854        for cell in cells {
1855            add_site(ErrorSite::Cell(cell));
1856            for plan in [pre_plan, final_plan] {
1857                if let Some(&producer) = plan.output_producers.get(&cell) {
1858                    Self::add_step_relationship_site(producer, &mut add_site);
1859                }
1860            }
1861        }
1862        sites
1863    }
1864
1865    /// Adds the relationship site associated with a selected method producer.
1866    fn add_step_relationship_site(step: PlanStep, add_site: &mut impl FnMut(ErrorSite)) {
1867        match step {
1868            PlanStep::Method(rel_id, _) => add_site(ErrorSite::Relationship(rel_id)),
1869            PlanStep::FilterReclamp(cell) => add_site(ErrorSite::Cell(cell)),
1870        }
1871    }
1872
1873    /// Executes `execution_order` once into transactional staged state.
1874    ///
1875    /// A method or filter step already evaluated by the conditional pre-plan reuses
1876    /// its staged result when the general plan contains the same compatible step.
1877    /// This is an internal optimization, not a callback invocation guarantee.
1878    /// Filter failures remain non-fatal and leave the staged cell untouched.
1879    ///
1880    /// - Precondition: each step belongs to this graph and has matching plan provenance.
1881    /// - Complexity: O(s × k²) structural work for s steps with at most k input/output
1882    ///   cells, plus callback costs and storage for staged output values.
1883    ///
1884    /// # Errors
1885    ///
1886    /// - `Error::MethodFailed` — a `PlanStep::Method` step's function returned an error,
1887    ///   or the method produced a different number of outputs than declared.
1888    /// - `Error::TypeMismatch` — a `PlanStep::Method` step's output runtime type does
1889    ///   not match the cell's registered type.
1890    fn execute_plan_staged(
1891        &self,
1892        execution_order: &[PlanStep],
1893        seeds: &Seeds,
1894        forced_outputs: &HashSet<CellId>,
1895        plan_provenance: &PlanProvenance,
1896        stage: &mut PropagationStage,
1897        filter_violations: &mut Vec<(CellId, FilterViolation)>,
1898    ) -> Result<(), Error> {
1899        for step in execution_order {
1900            match *step {
1901                PlanStep::Method(rel_id, method_idx) => {
1902                    if stage.executed_methods.contains(&(rel_id, method_idx)) {
1903                        let method = &self.relationships[rel_id].methods[method_idx];
1904                        for &output in &method.outputs {
1905                            stage.reclassify(
1906                                output,
1907                                method.inputs.contains(&output) || forced_outputs.contains(&output),
1908                            );
1909                        }
1910                        continue;
1911                    }
1912                    let method = &self.relationships[rel_id].methods[method_idx];
1913                    let outputs = {
1914                        let inputs: Vec<&dyn Any> = method
1915                            .inputs
1916                            .iter()
1917                            .map(|&id| {
1918                                if method.outputs.contains(&id) {
1919                                    seeds
1920                                        .get(&id)
1921                                        .map(|value| value.as_ref())
1922                                        .unwrap_or_else(|| stage.source(&self.cells, id))
1923                                } else {
1924                                    stage.effective(&self.cells, id)
1925                                }
1926                            })
1927                            .collect();
1928                        (method.function)(&inputs).map_err(|error| Error::MethodFailed {
1929                            error,
1930                            sites: vec![ErrorSite::Method(rel_id, method_idx)],
1931                        })?
1932                    };
1933
1934                    if outputs.len() != method.outputs.len() {
1935                        return Err(Error::MethodFailed {
1936                            error: anyhow::anyhow!(
1937                                "method produced {} outputs but relationship expects {}",
1938                                outputs.len(),
1939                                method.outputs.len()
1940                            ),
1941                            sites: vec![ErrorSite::Method(rel_id, method_idx)],
1942                        });
1943                    }
1944
1945                    for (&cell_id, new_value) in method.outputs.iter().zip(outputs) {
1946                        let found = new_value.as_ref().type_id();
1947                        let cell = &self.cells[cell_id];
1948                        if found != cell.type_id {
1949                            return Err(Error::TypeMismatch {
1950                                expected: cell.type_id,
1951                                found,
1952                                sites: vec![ErrorSite::Method(rel_id, method_idx)],
1953                            });
1954                        }
1955                        stage.write(
1956                            cell_id,
1957                            new_value,
1958                            method.inputs.contains(&cell_id) || forced_outputs.contains(&cell_id),
1959                        );
1960                    }
1961                    stage.executed_methods.insert((rel_id, method_idx));
1962                    stage
1963                        .step_provenance
1964                        .insert(*step, plan_provenance.steps[step].clone());
1965                }
1966                PlanStep::FilterReclamp(id) => {
1967                    if stage.executed_filters.contains(&id) {
1968                        continue;
1969                    }
1970                    let filter = self.cells[id]
1971                        .filter
1972                        .as_ref()
1973                        .expect("plan() only emits FilterReclamp for a filtered cell");
1974                    let args: Vec<&dyn Any> = filter
1975                        .args
1976                        .iter()
1977                        .map(|&a| stage.effective(&self.cells, a))
1978                        .collect();
1979                    let current = stage.source(&self.cells, id);
1980                    match (filter.function)(current, &args) {
1981                        Ok(v) if v.as_ref().type_id() == self.cells[id].type_id => {
1982                            stage.write(id, v, true);
1983                        }
1984                        Ok(_) => filter_violations.push((
1985                            id,
1986                            FilterViolation::Failed(anyhow::anyhow!(
1987                                "filter returned a value of a different type than the cell"
1988                            )),
1989                        )),
1990                        Err(error) => {
1991                            filter_violations.push((id, FilterViolation::Failed(error)));
1992                        }
1993                    }
1994                    stage.executed_filters.insert(id);
1995                    stage
1996                        .step_provenance
1997                        .insert(*step, plan_provenance.steps[step].clone());
1998                }
1999            }
2000        }
2001        Ok(())
2002    }
2003
2004    /// Assigns derived-cell strengths after executing a selected plan.
2005    ///
2006    /// Walks `execution_order` and assigns a decrementing counter (starting at
2007    /// `0x7FFF_FFFF_FFFF_FFFF`) to each output cell of each selected method, in
2008    /// execution order. Cells evaluated first receive the highest derived strength.
2009    /// Source cells (not the output of any selected method) are not modified.
2010    ///
2011    /// A cell claimed *self-referencingly* (its claiming method reads the cell as one of
2012    /// its own inputs) keeps a live explicit strength rather than being demoted: such a
2013    /// method computes the cell from its own `source` aspiration (see
2014    /// [`crate::planner::evaluate_seeds`]), so an explicit `write()` to that cell is still
2015    /// the authority behind the value and must keep outranking never-written cells in
2016    /// later rounds. Demoting it would discard the edit the next time the cell is
2017    /// re-seeded.
2018    ///
2019    /// - Complexity: O(R·K²) where R is the number of entries and K is the maximum
2020    ///   inputs or outputs per method.
2021    /// - Postcondition: returns whether any cell's strength actually changed.
2022    fn post_process_strengths(&mut self, execution_order: &[PlanStep]) -> bool {
2023        let mut changed = false;
2024        let mut derived_strength = u64::MAX >> 1; // 0x7FFF_FFFF_FFFF_FFFF
2025        let mut seen: std::collections::HashSet<CellId> = std::collections::HashSet::new();
2026        for step in execution_order {
2027            let PlanStep::Method(rel_id, method_idx) = step else {
2028                continue;
2029            };
2030            if let Some(rel) = self.relationships.get(*rel_id)
2031                && let Some(method) = rel.methods.get(*method_idx)
2032            {
2033                for &output in &method.outputs {
2034                    let self_referencing = method.inputs.contains(&output);
2035                    if seen.insert(output)
2036                        && let Some(cell) = self.cells.get_mut(output)
2037                    {
2038                        if self_referencing && cell.has_explicit_strength() {
2039                            continue;
2040                        }
2041                        changed |= cell.strength != derived_strength;
2042                        cell.strength = derived_strength;
2043                        derived_strength = derived_strength.saturating_sub(1);
2044                    }
2045                }
2046            }
2047        }
2048        changed
2049    }
2050
2051    /// Publishes a fully validated propagation stage and returns previously derived cells.
2052    ///
2053    /// Clears every old derived override, applies staged source and derived writes, and
2054    /// records each staged output in [`Sheet::changed`]. No callback is evaluated here.
2055    ///
2056    /// - Complexity: O(cells).
2057    fn commit_stage(&mut self, stage: PropagationStage) -> Vec<CellId> {
2058        let previously_derived: Vec<CellId> = self
2059            .cells
2060            .iter()
2061            .filter(|(_, cell)| cell.derived.is_some())
2062            .map(|(id, _)| id)
2063            .collect();
2064        for (_, cell) in self.cells.iter_mut() {
2065            cell.derived = None;
2066        }
2067        for (id, value) in stage.sources {
2068            self.cells[id].source = value;
2069        }
2070        for (id, value) in stage.derived {
2071            self.cells[id].derived = Some(value);
2072        }
2073        for id in stage.changed {
2074            let cell = &mut self.cells[id];
2075            if !cell.changed {
2076                cell.changed = true;
2077                self.changed_cells.push(id);
2078            }
2079        }
2080        previously_derived
2081    }
2082
2083    /// Updates the sheet using eligible prepared plans or newly selected methods.
2084    ///
2085    /// Validates static guard independence before mutating state, then clears the
2086    /// changed-cell set from the previous `propagate()` call before staged planning.
2087    /// After propagation, call [`Sheet::changed`] to inspect which cells were updated,
2088    /// and [`Sheet::clear_changed`] when done.
2089    ///
2090    /// Method, filter, conditional-expression, and requirement callbacks must be
2091    /// purely functional. There is no guarantee that any callback executes, or when
2092    /// or how often it executes; results may be reused or recomputed while preserving
2093    /// cell values and diagnostics. The phases below describe the current algorithm,
2094    /// not a callback invocation contract.
2095    ///
2096    /// Retains at most one prepared unconditional plan and one prepared main plan,
2097    /// each with its own released sources and active relationships. Successful writes
2098    /// to released sources preserve that phase's eligibility; writes to claimed cells
2099    /// (including self-referencing claimants) discard it. Filter reclamps do not claim
2100    /// released sources. Actual structural mutations discard both entries.
2101    /// Prepared plans retain structural seed recipes, not values from earlier calls.
2102    ///
2103    /// **Phase 0 — Staging:** propagation starts from each cell's source value, excluding
2104    /// every derived override from the previous round, but does not mutate live cells.
2105    ///
2106    /// **Phase 1 — Pre-plan:** when conditionals exist, an eligible prepared unconditional
2107    /// plan is obtained or a new one is prepared. Its selected guard-prerequisite cone
2108    /// executes so derived guard values are current before branch evaluation.
2109    /// Unrelated filters remain deferred until the final active plan, after the relationships
2110    /// that produce their arguments.
2111    ///
2112    /// **Phase 2 — Conditional evaluation:** each conditional's match cell value is
2113    /// read and compared against branch keys; the active relationship set is built.
2114    ///
2115    /// **Phase 3 — General plan:** an eligible prepared main plan is obtained for the active
2116    /// set, or the Adam algorithm selects and prepares a new one. Seed-cycle
2117    /// validation and method execution consume staged Phase 1 values. A callback already
2118    /// evaluated in Phase 1 is reused only when the final plan preserves its selected
2119    /// method, input producers, and output classification. A mismatched plan returns a
2120    /// conservative conflict rather than committing stale values. This
2121    /// boundary may reject a sheet even when another assignment could have avoided the mismatch;
2122    /// propagation reports the implicated sites instead of attempting that alternate assignment.
2123    /// A Phase 1 seed callback is likewise reused only when every input reads the same staged
2124    /// source version, recursive seed result, or accumulated value; otherwise propagation
2125    /// returns a conflict instead of reusing a stale seed.
2126    ///
2127    /// **Phase 4 — Commit and strength post-processing:** after planning, seed
2128    /// validation, and staged method execution all succeed, staged writes are published
2129    /// atomically. Derived cells then receive low-order strengths in evaluation order,
2130    /// enforcing the stability invariant. A cell claimed self-referencingly keeps any
2131    /// live explicit strength instead, since its own written value is still the authority
2132    /// behind the result.
2133    ///
2134    /// **Phase 5 — Reversion change-tracking:** a cell whose derived override existed
2135    /// before this round but wasn't reclaimed by any method this round has effectively
2136    /// reverted to its source value (e.g. its forcing conditional went inactive); it is
2137    /// marked changed even though no method wrote to it this round.
2138    ///
2139    /// **Phase 6 — Requirement evaluation:** every registered requirement is evaluated
2140    /// against current cell values, rebuilding `last_requirement_violations` from
2141    /// scratch, so [`Sheet::cell_requirements_valid`] and [`Sheet::violated_requirements`]
2142    /// reflect this round.
2143    ///
2144    /// Filter failures remain diagnostics rather than propagation errors, and diagnostic
2145    /// results use current values.
2146    ///
2147    /// - Complexity: on a cache miss, prerequisite-cone indexing, traversal, and producer
2148    ///   preparation take O(V + E) per selected plan, distinct from assignment matching.
2149    ///   Cache hits omit this preparation, assignment matching, global strength sorting,
2150    ///   and complete seed-elimination replay. Active-set comparison takes expected O(R)
2151    ///   for R active relationships; a pending strength certificate takes O(C) for C cells.
2152    ///   Current strength gates and local sibling sorts still run (O(S log S) comparisons
2153    ///   for S siblings, with structural signature comparison costs). Fresh staged
2154    ///   execution, seed evaluation, commit, and diagnostics retain their costs.
2155    ///   Discarding final owners of prepared artifacts of total size A can take O(A).
2156    ///
2157    /// # Errors
2158    ///
2159    /// - `Error::DependencyCycle` — a filter or conditional guard edge lies on a cycle in
2160    ///   the static dependency graph.
2161    /// - `Error::Conflict` — no valid method assignment exists, or a pre-executed guard
2162    ///   prerequisite is incompatible with the final selected plan.
2163    /// - `Error::SeedCycle` — sibling seed dependencies form a non-self cycle.
2164    /// - `Error::MethodFailed` — a method's function returned an error, a method
2165    ///   produced the wrong number of outputs, or a requirement's function returned
2166    ///   an error. A requirement error includes its governed cell as an error site.
2167    /// - `Error::TypeMismatch` — a method output's runtime type does not match the
2168    ///   cell's registered type.
2169    pub fn propagate(&mut self) -> Result<(), Error> {
2170        let mut pre = self.pre_plan_cache.take();
2171        let mut main = self.main_plan_cache.take();
2172        let result = self.propagate_with_caches(&mut pre, &mut main);
2173        if result.is_ok() {
2174            self.pre_plan_cache = pre;
2175            self.main_plan_cache = main;
2176        }
2177        result
2178    }
2179
2180    /// Returns the index of the method selected for `rel` in the last successful propagation.
2181    ///
2182    /// Returns `None` if no propagation has run yet, `rel` is not in the cached plan,
2183    /// or `rel` was added after the last `propagate()` call.
2184    ///
2185    /// - Complexity: O(S) worst-case time for S completed execution steps; O(1) space.
2186    ///
2187    /// # Examples
2188    /// See the [graph example](crate::sheet).
2189    pub fn selected_method(&self, rel: RelationshipId) -> Option<usize> {
2190        self.last_plan
2191            .as_ref()?
2192            .plan
2193            .execution_order
2194            .iter()
2195            .find_map(|step| match step {
2196                PlanStep::Method(r, idx) if *r == rel => Some(*idx),
2197                _ => None,
2198            })
2199    }
2200
2201    /// Returns the input cells of method `idx` in relationship `rel`.
2202    ///
2203    /// Returns `None` if `rel` is not a live relationship or `idx` is out of bounds.
2204    ///
2205    /// # Examples
2206    /// See the [graph example](crate::sheet).
2207    pub fn method_inputs(&self, rel: RelationshipId, idx: usize) -> Option<&[CellId]> {
2208        self.relationships
2209            .get(rel)?
2210            .methods
2211            .get(idx)
2212            .map(|m| m.inputs.as_slice())
2213    }
2214
2215    /// Returns the output cells of method `idx` in relationship `rel`.
2216    ///
2217    /// Returns `None` if `rel` is not a live relationship or `idx` is out of bounds.
2218    ///
2219    /// # Examples
2220    /// See the [graph example](crate::sheet).
2221    pub fn method_outputs(&self, rel: RelationshipId, idx: usize) -> Option<&[CellId]> {
2222        self.relationships
2223            .get(rel)?
2224            .methods
2225            .get(idx)
2226            .map(|m| m.outputs.as_slice())
2227    }
2228
2229    /// Returns `true` if `id` was not written by any selected method in the last successful propagation.
2230    ///
2231    /// Returns `false` if no propagation has succeeded yet. This display query does not
2232    /// determine prepared-plan eligibility.
2233    ///
2234    /// Cells absent from the completed plan, including newly added cells, return `true`.
2235    ///
2236    /// - Complexity: Expected O(1).
2237    ///
2238    /// # Examples
2239    /// See the [graph example](crate::sheet).
2240    pub fn is_source(&self, id: CellId) -> bool {
2241        let Some(plan) = &self.last_plan else {
2242            return false;
2243        };
2244        !plan.certificate.contains_cell(id) || plan.certificate.is_released(id)
2245    }
2246
2247    /// Iterates snapshot sources contributing to `results` in the last successful plan.
2248    ///
2249    /// An empty `results` slice selects the entire sheet: unclaimed cells and outputs
2250    /// read by their own selected method. Otherwise, selects only those sources in
2251    /// the requested cells' selected producer paths, including conforming filter
2252    /// arguments and conditional selectors governing those paths. Unselected methods'
2253    /// inputs and diagnostic-only filter arguments do not contribute.
2254    ///
2255    /// Each source appears once, in unspecified order, even when results repeat or
2256    /// share dependencies. Pure derived outputs and `Out` cells are excluded.
2257    /// Returns an empty iterator before any successful propagation. Like
2258    /// [`Self::is_source`], classification describes the last successful plan;
2259    /// newly added non-`Out` cells count as unclaimed.
2260    ///
2261    /// To capture a settled result, iterate after successful propagation and before
2262    /// further edits, reading effective values with [`Self::read`], not the
2263    /// aspirations returned by [`Self::source`]. Clients own baseline comparison,
2264    /// relevance filtering, stable cell identity, and typed serialization.
2265    ///
2266    /// Replay writes all captured values in any order as one batch before
2267    /// propagation. For a settled solution satisfying its relationships and filters,
2268    /// a compatible graph with matching uncaptured inputs reproduces the requested
2269    /// effective values (or all values when `results` is empty). Relationship methods
2270    /// must preserve an already-satisfied assignment, and self-referencing methods
2271    /// and filters must be idempotent;
2272    /// these caller contracts make the solution a fixed point even if replay
2273    /// selects different methods. No strengths or write history need to be captured.
2274    /// A snapshot does not preserve original aspirations, priority history,
2275    /// diagnostics, or behavior under subsequent edits, including spring-back.
2276    ///
2277    /// - Precondition: every result is a live cell belonging to this sheet.
2278    /// - Complexity: empty results take O(C + S + Σ(I × O)) traversal time and O(1)
2279    ///   auxiliary space, for C cells, S selected steps, and each selected method's
2280    ///   I inputs and O outputs. Nonempty results add expected O(N + V + E + V × G)
2281    ///   time and O(N + V + E) auxiliary space, for N requested results, V reachable
2282    ///   value/producer pairs, E their dependency edges, and G the total size of the
2283    ///   registered conditional graph. Does not copy cell values.
2284    ///
2285    /// # Examples
2286    ///
2287    /// ```
2288    /// use adam_rs::Sheet;
2289    /// let mut sheet = Sheet::new();
2290    /// let width = sheet.add_source(10_i32);
2291    /// sheet.add_source(false);
2292    /// sheet.propagate()?;
2293    /// assert_eq!(sheet.source_cells(&[]).count(), 2);
2294    /// let capture = sheet.source_cells(&[width])
2295    ///     .map(|id| Ok((id, *sheet.read::<i32>(id)?)))
2296    ///     .collect::<Result<Vec<_>, adam_rs::Error>>()?;
2297    /// assert_eq!(capture, [(width, 10)]);
2298    /// // Use host-defined names or schema keys, not live CellIds, for persistence.
2299    /// # Ok::<(), adam_rs::Error>(())
2300    /// ```
2301    pub fn source_cells(&self, results: &[CellId]) -> impl Iterator<Item = CellId> + use<'_> {
2302        debug_assert!(results.iter().all(|id| self.cells.contains_key(*id)));
2303        let contributors = (!results.is_empty()).then(|| self.capture_contributors(results));
2304        let unclaimed = self.cells.keys().filter(|&id| self.is_source(id));
2305        let self_referencing = self
2306            .last_plan
2307            .iter()
2308            .flat_map(|prepared| &prepared.plan.execution_order)
2309            .filter_map(|step| match *step {
2310                PlanStep::Method(id, index) => Some(&self.relationships[id].methods[index]),
2311                PlanStep::FilterReclamp(_) => None,
2312            })
2313            .flat_map(|method| {
2314                method
2315                    .outputs
2316                    .iter()
2317                    .copied()
2318                    .filter(|id| method.inputs.contains(id))
2319            });
2320        unclaimed.chain(self_referencing).filter(move |&id| {
2321            self.cells[id].kind != CellKind::Out
2322                && contributors
2323                    .as_ref()
2324                    .is_none_or(|cells| cells.contains(&id))
2325        })
2326    }
2327
2328    /// Returns the source leaves of `results`' selected value and control dependencies.
2329    ///
2330    /// Producer identities distinguish a filter's incoming source from its effective
2331    /// output. Self-referencing inputs terminate at their captured effective value,
2332    /// rather than introducing a cycle or following alternative seed methods.
2333    ///
2334    /// - Precondition: every result is a live cell belonging to this sheet.
2335    /// - Complexity: expected O(N + V + E + V × G) time and O(N + V + E) space,
2336    ///   for N results, V reachable value/producer pairs, E dependency edges, and
2337    ///   G the total size of the registered conditional graph.
2338    fn capture_contributors(&self, results: &[CellId]) -> HashSet<CellId> {
2339        let Some(prepared) = &self.last_plan else {
2340            return HashSet::new();
2341        };
2342        let provenance = &prepared.provenance;
2343        let current_value = |cell| (cell, provenance.output_producers.get(&cell).copied());
2344        let mut pending: Vec<_> = results.iter().copied().map(current_value).collect();
2345        let mut visited = HashSet::new();
2346        let mut contributors = HashSet::new();
2347
2348        while let Some((cell, producer)) = pending.pop() {
2349            if !visited.insert((cell, producer)) {
2350                continue;
2351            }
2352            pending.extend(
2353                self.conditionals
2354                    .values()
2355                    .filter(|conditional| {
2356                        conditional
2357                            .branches
2358                            .iter()
2359                            .flat_map(|branch| branch.relationships.iter())
2360                            .chain(conditional.default.iter())
2361                            .any(|&relationship| {
2362                                matches!(
2363                                    producer,
2364                                    Some(PlanStep::Method(selected, _)) if selected == relationship
2365                                ) || (!prepared.active.contains(&relationship)
2366                                    && self.relationships[relationship]
2367                                        .methods
2368                                        .iter()
2369                                        .any(|method| method.outputs.contains(&cell)))
2370                            })
2371                    })
2372                    .flat_map(|conditional| conditional.match_cells().iter().copied())
2373                    .map(current_value),
2374            );
2375            let Some(step) = producer else {
2376                contributors.insert(cell);
2377                continue;
2378            };
2379            let step_provenance = &provenance.steps[&step];
2380            let inputs = match step {
2381                PlanStep::Method(relationship, index) => {
2382                    &self.relationships[relationship].methods[index].inputs
2383                }
2384                PlanStep::FilterReclamp(filtered) => {
2385                    pending.push((filtered, step_provenance.source_producer));
2386                    &self.cells[filtered]
2387                        .filter
2388                        .as_ref()
2389                        .expect("planned filter reclamps have an attached filter")
2390                        .args
2391                }
2392            };
2393            pending.extend(
2394                inputs
2395                    .iter()
2396                    .copied()
2397                    .zip(step_provenance.input_producers.iter().copied()),
2398            );
2399        }
2400        contributors
2401    }
2402
2403    /// Returns whether `id` belongs to the last successful plan's forced pure-output set.
2404    ///
2405    /// The planner intersects pure outputs across viable methods and eliminates
2406    /// alternatives that conflict with another relationship's forced outputs until
2407    /// the classification stabilizes. A forced pure output's producer does not read
2408    /// that cell as a self-referencing input.
2409    ///
2410    /// A conditionally forced cell retains its pre-forcing priority and source
2411    /// aspiration for release; see [the forcing outcomes](crate::sheet#forced-self-referencing-and-reversible-outcomes).
2412    ///
2413    /// This does not decide whether some assignment can leave the cell unclaimed.
2414    /// A mandatory self-referencing writer claims its output exclusively but remains
2415    /// outside this classification; its editable source aspiration can affect its seed.
2416    ///
2417    /// Returns `false` if no propagation has run yet.
2418    ///
2419    /// # Examples
2420    /// See the [graph example](crate::sheet).
2421    pub fn is_forced(&self, id: CellId) -> bool {
2422        self.last_plan
2423            .as_ref()
2424            .is_some_and(|prepared| prepared.plan.forced_outputs.contains(&id))
2425    }
2426
2427    /// Iterates cells that are forced (see [`Sheet::is_forced`]) as of the last
2428    /// successful `propagate()` call.
2429    ///
2430    /// - Complexity: O(n) where n is the number of forced cells.
2431    ///
2432    /// # Examples
2433    /// See the [graph example](crate::sheet).
2434    pub fn forced_cells(&self) -> impl Iterator<Item = CellId> + '_ {
2435        self.last_plan
2436            .iter()
2437            .flat_map(|prepared| prepared.plan.forced_outputs.iter().copied())
2438    }
2439
2440    /// Returns `true` if `id` had exactly one viable method as of the last successful
2441    /// `propagate()` call — the planner has no alternative method to choose for this
2442    /// relationship, regardless of cell strength.
2443    ///
2444    /// Returns `false` if no propagation has run yet.
2445    ///
2446    /// # Examples
2447    /// See the [graph example](crate::sheet).
2448    pub fn is_relationship_forced(&self, id: RelationshipId) -> bool {
2449        self.last_plan
2450            .as_ref()
2451            .is_some_and(|prepared| prepared.plan.forced_relationships.contains(&id))
2452    }
2453
2454    /// Iterates relationships that are forced (see [`Sheet::is_relationship_forced`])
2455    /// as of the last successful `propagate()` call.
2456    ///
2457    /// - Complexity: O(n) where n is the number of forced relationships.
2458    ///
2459    /// # Examples
2460    /// See the [graph example](crate::sheet).
2461    pub fn forced_relationships(&self) -> impl Iterator<Item = RelationshipId> + '_ {
2462        self.last_plan
2463            .iter()
2464            .flat_map(|prepared| prepared.plan.forced_relationships.iter().copied())
2465    }
2466
2467    /// Iterates all live conditional IDs in the sheet.
2468    ///
2469    /// - Complexity: O(n) where n is the number of conditionals.
2470    ///
2471    /// # Examples
2472    /// See the [graph example](crate::sheet).
2473    pub fn conditionals(&self) -> impl Iterator<Item = ConditionalId> + '_ {
2474        self.conditionals.keys()
2475    }
2476
2477    /// Iterates all live `Out`-kind cells in the sheet.
2478    ///
2479    /// - Complexity: O(n) where n is the number of cells.
2480    ///
2481    /// # Examples
2482    /// See the [graph example](crate::sheet).
2483    pub fn out_cells(&self) -> impl Iterator<Item = CellId> + '_ {
2484        self.cells
2485            .iter()
2486            .filter(|(_, c)| c.kind == CellKind::Out)
2487            .map(|(id, _)| id)
2488    }
2489
2490    /// Returns the match cells for conditional `id`: a single cell for a plain match
2491    /// subject, or every input of a [`MatchExpr`] match subject.
2492    ///
2493    /// Returns `None` if `id` is not a live conditional in this sheet.
2494    ///
2495    /// # Examples
2496    /// See the [graph example](crate::sheet).
2497    pub fn conditional_match_cells(&self, id: ConditionalId) -> Option<&[CellId]> {
2498        self.conditionals.get(id).map(|c| c.match_cells())
2499    }
2500
2501    /// Returns the number of named branches in conditional `id`.
2502    ///
2503    /// Returns `None` if `id` is not a live conditional in this sheet.
2504    ///
2505    /// # Examples
2506    /// See the [graph example](crate::sheet).
2507    pub fn conditional_branch_count(&self, id: ConditionalId) -> Option<usize> {
2508        self.conditionals.get(id).map(|c| c.branches.len())
2509    }
2510
2511    /// Returns the relationship IDs for branch `branch` of conditional `id`.
2512    ///
2513    /// Returns `None` if `id` is not a live conditional, or `branch` is out of bounds.
2514    ///
2515    /// # Examples
2516    /// See the [graph example](crate::sheet).
2517    pub fn conditional_branch_relationships(
2518        &self,
2519        id: ConditionalId,
2520        branch: usize,
2521    ) -> Option<&[RelationshipId]> {
2522        self.conditionals
2523            .get(id)?
2524            .branches
2525            .get(branch)
2526            .map(|b| b.relationships.as_slice())
2527    }
2528
2529    /// Returns the default relationship IDs for conditional `id`.
2530    ///
2531    /// These relationships are active when no named branch key matches the match cell.
2532    /// Returns `None` if `id` is not a live conditional in this sheet.
2533    ///
2534    /// # Examples
2535    /// See the [graph example](crate::sheet).
2536    pub fn conditional_default_relationships(
2537        &self,
2538        id: ConditionalId,
2539    ) -> Option<&[RelationshipId]> {
2540        self.conditionals.get(id).map(|c| c.default.as_slice())
2541    }
2542
2543    /// Returns the index of the currently matching branch for conditional `id`.
2544    ///
2545    /// Evaluates branch keys against the match subject's current value in definition
2546    /// order; returns the index of the first matching branch. Returns `Ok(None)` if no
2547    /// branch key matches (the default branch is active) or if `id` is not a live
2548    /// conditional.
2549    ///
2550    /// # Errors
2551    ///
2552    /// - `Error::MethodFailed` — `id` is a live, expression-sourced conditional whose
2553    ///   function returned an error. The original cause and `ErrorSite::Conditional(id)`
2554    ///   identify the failing execution.
2555    ///
2556    /// - Complexity: O(B·K) where B = branches, K = keys per branch.
2557    ///
2558    /// # Examples
2559    /// See the [graph example](crate::sheet).
2560    pub fn conditional_active_branch(&self, id: ConditionalId) -> Result<Option<usize>, Error> {
2561        let Some(cond) = self.conditionals.get(id) else {
2562            return Ok(None);
2563        };
2564        let value = self.evaluate_match_source(id, cond)?;
2565        let value_ref = value.as_dyn();
2566        let eq_fn = self.match_eq_fn(cond);
2567        Ok(cond
2568            .branches
2569            .iter()
2570            .enumerate()
2571            .find(|(_, branch)| branch.keys.iter().any(|key| eq_fn(value_ref, key.as_ref())))
2572            .map(|(i, _)| i))
2573    }
2574}
2575
2576impl Default for Sheet {
2577    /// Returns `Sheet::new()`.
2578    fn default() -> Self {
2579        Self::new()
2580    }
2581}
2582
2583#[cfg(test)]
2584mod tests {
2585    use crate::{
2586        CellKind, ConditionalId, Error, MatchExpr, Method, Requirement, Sheet,
2587        cell::CellId,
2588        error::ErrorSite,
2589        filter::{Filter, FilterKind, FilterViolation},
2590        planner::{PlanStep, Seeds},
2591        relationship::RelationshipId,
2592    };
2593    use std::any::{Any, TypeId};
2594    use std::collections::{HashMap, HashSet};
2595
2596    /// Starts an empty default sheet and reports registered concrete identities.
2597    #[test]
2598    fn default_and_type_id_obey_empty_and_registration_contracts() {
2599        let mut sheet = Sheet::default();
2600        assert_eq!(sheet.cells().count(), 0);
2601        assert_eq!(sheet.relationships().count(), 0);
2602        assert_eq!(sheet.conditionals().count(), 0);
2603        assert_eq!(sheet.changed().count(), 0);
2604        assert_eq!(sheet.cell_type_id(CellId::default()), None);
2605        let cell = sheet.add_cell(String::new());
2606        assert_eq!(sheet.cell_type_id(cell), Some(TypeId::of::<String>()));
2607    }
2608
2609    /// Rebuilds a fresh sheet after a staged type error.
2610    #[test]
2611    fn late_output_type_failure_requires_rebuilding_the_sheet() {
2612        let make_sheet = |source_value| {
2613            let mut sheet = Sheet::new();
2614            let source = sheet.add_cell(source_value);
2615            let a = sheet.add_cell(0_i32);
2616            let b = sheet.add_cell(0_i32);
2617            sheet
2618                .add_relationship(vec![Method::new(
2619                    vec![source],
2620                    vec![a, b],
2621                    vec![TypeId::of::<i32>()],
2622                    vec![TypeId::of::<i32>(); 2],
2623                    |args| {
2624                        let value = *args[0].downcast_ref::<i32>().unwrap();
2625                        let last: Box<dyn Any> = if value < 0 {
2626                            Box::new(false)
2627                        } else {
2628                            Box::new(value)
2629                        };
2630                        Ok(vec![Box::new(value), last])
2631                    },
2632                )])
2633                .unwrap();
2634            (sheet, source, a, b)
2635        };
2636
2637        let (mut sheet, source, _, _) = make_sheet(5_i32);
2638        sheet.propagate().unwrap();
2639        sheet.write(source, -9_i32).unwrap();
2640        assert!(matches!(sheet.propagate(), Err(Error::TypeMismatch { .. })));
2641        drop(sheet);
2642
2643        let (mut rebuilt, _, a, b) = make_sheet(12_i32);
2644        rebuilt.propagate().unwrap();
2645        assert_eq!(*rebuilt.read::<i32>(a).unwrap(), 12);
2646        assert_eq!(*rebuilt.read::<i32>(b).unwrap(), 12);
2647    }
2648
2649    #[test]
2650    /// Checks the ordinary kind assigned to a new editable cell.
2651    fn add_cell_has_cell_kind() {
2652        let mut sheet = Sheet::new();
2653        let a = sheet.add_cell(0_i32);
2654        assert_eq!(sheet.cell_kind(a), Some(CellKind::Cell));
2655    }
2656
2657    #[test]
2658    /// Checks the source-only kind assigned to a new source.
2659    fn add_source_has_source_kind() {
2660        let mut sheet = Sheet::new();
2661        let a = sheet.add_source(0_i32);
2662        assert_eq!(sheet.cell_kind(a), Some(CellKind::Source));
2663    }
2664
2665    #[test]
2666    /// Checks absence of kind metadata for an unknown cell.
2667    fn cell_kind_returns_none_for_invalid_id() {
2668        let mut sheet = Sheet::new();
2669        sheet.add_cell(0_i32); // occupies slotmap index 0 in `sheet`
2670        let mut other = Sheet::new();
2671        other.add_cell(0_i32); // index 0 in `other`
2672        let bogus = other.add_cell(0_i32); // index 1 in `other` -- out of range for `sheet`,
2673        // which only ever allocated index 0, so this is
2674        // guaranteed invalid regardless of generation
2675        // (a same-index key from a second fresh SlotMap
2676        // would otherwise collide with `sheet`'s own key)
2677        assert_eq!(sheet.cell_kind(bogus), None);
2678    }
2679
2680    #[test]
2681    /// Checks that a later plain staged write replaces an earlier derived override.
2682    fn execute_plan_staged_clears_a_stale_override_on_a_later_plain_write() {
2683        let mut sheet = Sheet::new();
2684        let x = sheet.add_cell(8_i32);
2685        let y = sheet.add_cell(2_i32);
2686        let z = sheet.add_cell(10_i32);
2687        let self_ref = sheet
2688            .add_relationship(vec![
2689                Method::from_fn_2_1([x, y], x, |a: &i32, b: &i32| Ok((*a).min(*b))),
2690                Method::from_fn_2_1([x, y], y, |a: &i32, b: &i32| Ok((*a).max(*b))),
2691            ])
2692            .unwrap();
2693        let plain = sheet
2694            .add_relationship(vec![Method::from_fn_1_1(z, x, |v: &i32| {
2695                v.checked_add(1).ok_or_else(|| anyhow::anyhow!("overflow"))
2696            })])
2697            .unwrap();
2698
2699        let no_seeds: Seeds = HashMap::new();
2700        let no_forced: HashSet<CellId> = HashSet::new();
2701        let mut stage = super::PropagationStage::default();
2702        let mut plan = crate::planner::plan(
2703            &sheet.cells,
2704            &sheet.relationships,
2705            &HashSet::from([self_ref]),
2706        )
2707        .unwrap();
2708        plan.execution_order = vec![PlanStep::Method(self_ref, 0)];
2709        plan.forced_outputs.clear();
2710        let provenance = sheet.plan_provenance(&plan);
2711        sheet
2712            .execute_plan_staged(
2713                &[PlanStep::Method(self_ref, 0)],
2714                &no_seeds,
2715                &no_forced,
2716                &provenance,
2717                &mut stage,
2718                &mut Vec::new(),
2719            )
2720            .unwrap();
2721        assert_eq!(
2722            stage.effective(&sheet.cells, x).downcast_ref::<i32>(),
2723            Some(&2)
2724        );
2725        assert_eq!(*sheet.read::<i32>(x).unwrap(), 8);
2726
2727        sheet.write(z, 41_i32).unwrap();
2728        let mut plan =
2729            crate::planner::plan(&sheet.cells, &sheet.relationships, &HashSet::from([plain]))
2730                .unwrap();
2731        plan.forced_outputs.clear();
2732        let provenance = sheet.plan_provenance(&plan);
2733        sheet
2734            .execute_plan_staged(
2735                &[PlanStep::Method(plain, 0)],
2736                &no_seeds,
2737                &no_forced,
2738                &provenance,
2739                &mut stage,
2740                &mut Vec::new(),
2741            )
2742            .unwrap();
2743        assert_eq!(
2744            stage.effective(&sheet.cells, x).downcast_ref::<i32>(),
2745            Some(&42)
2746        );
2747        sheet.commit_stage(stage);
2748        assert_eq!(*sheet.read::<i32>(x).unwrap(), 42);
2749    }
2750
2751    #[test]
2752    /// Checks rejection of an unknown conditional match cell.
2753    fn add_conditional_returns_error_for_invalid_cell() {
2754        let mut sheet = Sheet::new();
2755        let result = sheet.add_conditional(
2756            MatchExpr::cell(CellId::default()),
2757            vec![(vec![0_i32], vec![])],
2758            vec![],
2759        );
2760        assert!(matches!(result, Err(Error::InvalidId)));
2761    }
2762
2763    #[test]
2764    /// Checks rejection of branch keys with the wrong match type.
2765    fn add_conditional_returns_invalid_conditional_for_type_mismatch() {
2766        let mut sheet = Sheet::new();
2767        let a = sheet.add_cell(0_i32);
2768        // Branch keys are f64 but cell holds i32.
2769        let result =
2770            sheet.add_conditional(MatchExpr::cell(a), vec![(vec![0.0_f64], vec![])], vec![]);
2771        assert!(matches!(result, Err(Error::InvalidConditional { .. })));
2772    }
2773
2774    #[test]
2775    /// Checks rejection of an unknown conditional relationship.
2776    fn add_conditional_returns_invalid_conditional_for_missing_relationship() {
2777        let mut sheet = Sheet::new();
2778        let a = sheet.add_cell(0_i32);
2779        let result = sheet.add_conditional(
2780            MatchExpr::cell(a),
2781            vec![(vec![0_i32], vec![RelationshipId::default()])],
2782            vec![],
2783        );
2784        assert!(matches!(result, Err(Error::InvalidConditional { .. })));
2785    }
2786
2787    #[test]
2788    /// Checks the match-cell site in a conditional dependency-cycle error.
2789    fn dependency_cycle_from_a_conditional_names_the_match_cell() {
2790        let mut sheet = Sheet::new();
2791        let a = sheet.add_cell(0_i32);
2792        let b = sheet.add_cell(0_i32);
2793        // Relationship has two methods and involves `a` (the match cell).
2794        let rel = sheet
2795            .add_relationship(vec![
2796                Method::from_fn_1_1(a, b, |x: &i32| Ok(*x)),
2797                Method::from_fn_1_1(b, a, |x: &i32| Ok(*x)),
2798            ])
2799            .unwrap();
2800        sheet
2801            .add_conditional(MatchExpr::cell(a), vec![(vec![0_i32], vec![rel])], vec![])
2802            .unwrap();
2803        let err = sheet.validate().unwrap_err();
2804        assert!(matches!(err, Error::DependencyCycle { .. }));
2805        assert!(err.sites().contains(&ErrorSite::Cell(a)));
2806    }
2807
2808    #[test]
2809    /// Checks rejection of a branch that can derive an upstream match dependency.
2810    fn add_conditional_returns_error_when_branch_rel_writes_a_cell_upstream_of_match_cell() {
2811        let mut sheet = Sheet::new();
2812        let a = sheet.add_cell(0_i32);
2813        let b = sheet.add_cell(0_i32);
2814        let p = sheet.add_cell(0_i32);
2815        // Unconditional: a → p  (a contributes to match cell p).
2816        sheet
2817            .add_relationship(vec![Method::from_fn_1_1(a, p, |x: &i32| Ok(*x))])
2818            .unwrap();
2819        // Branch relationship has two methods and involves `a`, which feeds p.
2820        let rel = sheet
2821            .add_relationship(vec![
2822                Method::from_fn_1_1(a, b, |x: &i32| Ok(*x)),
2823                Method::from_fn_1_1(b, a, |x: &i32| Ok(*x)),
2824            ])
2825            .unwrap();
2826        sheet
2827            .add_conditional(MatchExpr::cell(p), vec![(vec![0_i32], vec![rel])], vec![])
2828            .unwrap();
2829        assert!(matches!(
2830            sheet.validate(),
2831            Err(Error::DependencyCycle { .. })
2832        ));
2833    }
2834
2835    #[test]
2836    /// Checks guard independence for every computed-match input.
2837    fn add_conditional_returns_error_when_branch_rel_writes_a_cell_upstream_of_either_expr_input() {
2838        let mut sheet = Sheet::new();
2839        let a = sheet.add_cell(0_i32);
2840        let b = sheet.add_cell(0_i32);
2841        let p = sheet.add_cell(0_i32);
2842        let q = sheet.add_cell(0_i32);
2843        // Unconditional: a → q  (a contributes to expr input q).
2844        sheet
2845            .add_relationship(vec![Method::from_fn_1_1(a, q, |x: &i32| Ok(*x))])
2846            .unwrap();
2847        // Branch relationship has two methods and involves `a`, which feeds q, one of the
2848        // match expression's two inputs (p, q).
2849        let rel = sheet
2850            .add_relationship(vec![
2851                Method::from_fn_1_1(a, b, |x: &i32| Ok(*x)),
2852                Method::from_fn_1_1(b, a, |x: &i32| Ok(*x)),
2853            ])
2854            .unwrap();
2855        let expr = MatchExpr::from_fn_2([p, q], |x: &i32, y: &i32| Ok(*x + *y));
2856        sheet
2857            .add_conditional(expr, vec![(vec![0_i32], vec![rel])], vec![])
2858            .unwrap();
2859        assert!(matches!(
2860            sheet.validate(),
2861            Err(Error::DependencyCycle { .. })
2862        ));
2863    }
2864
2865    #[test]
2866    /// Checks branch selection from a computed two-input subject.
2867    fn add_conditional_activates_branch_from_two_cell_expression() {
2868        let mut sheet = Sheet::new();
2869        let a = sheet.add_cell(false);
2870        let b = sheet.add_cell(false);
2871        let x = sheet.add_cell(0_i32);
2872        let y = sheet.add_cell(0_i32);
2873        let rel_true = sheet
2874            .add_relationship(vec![Method::from_fn_1_1(x, y, |v: &i32| Ok(*v))])
2875            .unwrap();
2876        let expr = MatchExpr::from_fn_2([a, b], |p: &bool, q: &bool| Ok(*p && *q));
2877        let cid = sheet
2878            .add_conditional(expr, vec![(vec![true], vec![rel_true])], vec![])
2879            .unwrap();
2880
2881        sheet.write(a, true).unwrap();
2882        sheet.write(b, false).unwrap();
2883        assert_eq!(sheet.conditional_active_branch(cid).unwrap(), None);
2884
2885        sheet.write(b, true).unwrap();
2886        assert_eq!(sheet.conditional_active_branch(cid).unwrap(), Some(0));
2887        assert_eq!(sheet.conditional_match_cells(cid).unwrap(), &[a, b]);
2888    }
2889
2890    #[test]
2891    /// Checks rejection of keys incompatible with the computed subject's output type.
2892    fn add_conditional_returns_invalid_conditional_for_expr_output_type_mismatch() {
2893        let mut sheet = Sheet::new();
2894        let a = sheet.add_cell(0_i32);
2895        let b = sheet.add_cell(0_i32);
2896        // Expression computes an i32, but branch keys below are f64.
2897        let expr = MatchExpr::from_fn_2([a, b], |x: &i32, y: &i32| Ok(x + y));
2898        let result = sheet.add_conditional::<f64>(expr, vec![(vec![0.0], vec![])], vec![]);
2899        assert!(matches!(result, Err(Error::InvalidConditional { .. })));
2900    }
2901
2902    #[test]
2903    /// Checks rejection of an unknown computed-subject input.
2904    fn add_conditional_returns_invalid_id_for_bad_expr_input_cell() {
2905        let mut sheet = Sheet::new();
2906        let a = sheet.add_cell(0_i32);
2907        let expr = MatchExpr::from_fn_2([a, CellId::default()], |x: &i32, y: &i32| Ok(x + y));
2908        let result = sheet.add_conditional::<i32>(expr, vec![], vec![]);
2909        assert!(matches!(result, Err(Error::InvalidId)));
2910    }
2911
2912    #[test]
2913    /// Checks propagation of a computed subject's callback failure.
2914    fn propagate_surfaces_method_failed_from_a_failing_match_expression() {
2915        let mut sheet = Sheet::new();
2916        let a = sheet.add_cell(0_i32);
2917        let expr = MatchExpr::from_fn_1(a, |_x: &i32| -> Result<i32, anyhow::Error> {
2918            Err(anyhow::anyhow!("boom"))
2919        });
2920        sheet
2921            .add_conditional::<i32>(expr, vec![(vec![0], vec![])], vec![])
2922            .unwrap();
2923        let result = sheet.propagate();
2924        assert!(matches!(result, Err(Error::MethodFailed { .. })));
2925    }
2926
2927    #[test]
2928    /// Checks acceptance of multi-method branches independent of the match cell.
2929    fn add_conditional_allows_multi_method_rel_not_involving_match_cell() {
2930        let mut sheet = Sheet::new();
2931        let mode = sheet.add_cell(0_i32);
2932        let a = sheet.add_cell(0_i32);
2933        let b = sheet.add_cell(0_i32);
2934        // Relationship has two methods but does not involve `mode` (the match cell).
2935        // Branch relationships whose outputs do not reach the match cell may have any
2936        // number of methods.
2937        let rel = sheet
2938            .add_relationship(vec![
2939                Method::from_fn_1_1(a, b, |x: &i32| Ok(*x)),
2940                Method::from_fn_1_1(b, a, |x: &i32| Ok(*x)),
2941            ])
2942            .unwrap();
2943        let result = sheet.add_conditional(
2944            MatchExpr::cell(mode),
2945            vec![(vec![0_i32], vec![rel])],
2946            vec![],
2947        );
2948        assert!(result.is_ok());
2949    }
2950
2951    #[test]
2952    /// Checks rejection of an explicit branch without keys.
2953    fn add_conditional_returns_invalid_conditional_for_empty_branch_keys() {
2954        let mut sheet = Sheet::new();
2955        let a = sheet.add_cell(0_i32);
2956        let b = sheet.add_cell(0_i32);
2957        let rel = sheet
2958            .add_relationship(vec![Method::from_fn_1_1(a, b, |x: &i32| Ok(*x))])
2959            .unwrap();
2960        // Empty key list is invalid.
2961        let result =
2962            sheet.add_conditional::<i32>(MatchExpr::cell(a), vec![(vec![], vec![rel])], vec![]);
2963        assert!(matches!(result, Err(Error::InvalidConditional { .. })));
2964    }
2965
2966    #[test]
2967    /// Checks duplicate conditional ownership rejection and its relationship site.
2968    fn invalid_conditional_duplicate_relationship_names_that_relationship() {
2969        let mut sheet = Sheet::new();
2970        let a = sheet.add_cell(0_i32);
2971        let b = sheet.add_cell(0_i32);
2972        let dup_rel = sheet
2973            .add_relationship(vec![Method::from_fn_1_1(a, b, |x: &i32| Ok(*x))])
2974            .unwrap();
2975        // Add dup_rel to the first conditional.
2976        sheet
2977            .add_conditional(
2978                MatchExpr::cell(a),
2979                vec![(vec![0_i32], vec![dup_rel])],
2980                vec![],
2981            )
2982            .unwrap();
2983        // Try to add the same relationship to a second conditional.
2984        let result = sheet.add_conditional(
2985            MatchExpr::cell(a),
2986            vec![(vec![1_i32], vec![dup_rel])],
2987            vec![],
2988        );
2989        let err = result.unwrap_err();
2990        assert!(matches!(err, Error::InvalidConditional { .. }));
2991        assert!(err.sites().contains(&ErrorSite::Relationship(dup_rel)));
2992    }
2993
2994    #[test]
2995    /// Checks registration and lookup of a valid conditional identity.
2996    fn add_conditional_returns_id_for_valid_input() {
2997        let mut sheet = Sheet::new();
2998        let a = sheet.add_cell(0_i32);
2999        let b = sheet.add_cell(0_i32);
3000        let rel = sheet
3001            .add_relationship(vec![Method::from_fn_1_1(a, b, |x: &i32| Ok(*x))])
3002            .unwrap();
3003        let cid = sheet
3004            .add_conditional(MatchExpr::cell(a), vec![(vec![0_i32], vec![rel])], vec![])
3005            .unwrap();
3006        assert_eq!(sheet.conditionals().collect::<Vec<_>>(), vec![cid]);
3007        assert_eq!(sheet.conditional_match_cells(cid), Some([a].as_slice()));
3008    }
3009
3010    #[test]
3011    /// Checks distinct identities for separate cell declarations.
3012    fn add_cell_returns_distinct_ids() {
3013        let mut sheet = Sheet::new();
3014        let a = sheet.add_cell(1_i32);
3015        let b = sheet.add_cell(2_i32);
3016        assert_ne!(a, b);
3017    }
3018
3019    #[test]
3020    /// Checks rejection of external writes to fixed Out cells.
3021    fn write_returns_invalid_cell_kind_for_an_output_cell() {
3022        let mut sheet = Sheet::new();
3023        let writer_input = sheet.add_cell(1_i32);
3024        let out_cell = sheet.add_cell(0_i32);
3025        let out = sheet
3026            .add_out(
3027                Method::from_fn_1_1(writer_input, out_cell, |x: &i32| Ok(*x)),
3028                vec![],
3029            )
3030            .unwrap();
3031        assert!(matches!(
3032            sheet.write(out, 5_i32),
3033            Err(Error::InvalidCellKind { .. })
3034        ));
3035    }
3036
3037    #[test]
3038    /// Checks rejection of method outputs targeting source-only cells.
3039    fn add_relationship_returns_invalid_cell_kind_when_a_source_cell_is_an_output() {
3040        let mut sheet = Sheet::new();
3041        let a = sheet.add_source(0_i32);
3042        let b = sheet.add_cell(0_i32);
3043        let result = sheet.add_relationship(vec![Method::from_fn_1_1(b, a, |x: &i32| Ok(*x))]);
3044        assert!(matches!(result, Err(Error::InvalidCellKind { .. })));
3045        assert_eq!(
3046            result.unwrap_err().sites().first().copied(),
3047            Some(ErrorSite::MethodIndex(0))
3048        );
3049    }
3050
3051    #[test]
3052    /// Checks method and cell sites for a rejected source-only output.
3053    fn invalid_cell_kind_names_the_method_and_the_source_output_cell() {
3054        let mut sheet = Sheet::new();
3055        let s = sheet.add_source(0_i32); // Source-kind cell
3056        // A Source-kind output is rejected.
3057        let err = sheet
3058            .add_relationship(vec![Method::from_fn_1_1(s, s, |x: &i32| Ok(*x))])
3059            .unwrap_err();
3060        let sites = err.sites();
3061        assert_eq!(sites[0], ErrorSite::MethodIndex(0));
3062        assert!(sites[1..].contains(&ErrorSite::Cell(s)));
3063    }
3064
3065    #[test]
3066    /// Checks acceptance of source-only cells as method inputs.
3067    fn add_relationship_allows_a_source_cell_as_an_input() {
3068        let mut sheet = Sheet::new();
3069        let a = sheet.add_source(5_i32);
3070        let b = sheet.add_cell(0_i32);
3071        let result = sheet.add_relationship(vec![Method::from_fn_1_1(a, b, |x: &i32| Ok(*x))]);
3072        assert!(result.is_ok());
3073    }
3074
3075    #[test]
3076    /// Checks external editing of source-only cells.
3077    fn write_succeeds_on_a_source_cell() {
3078        let mut sheet = Sheet::new();
3079        let a = sheet.add_source(0_i32);
3080        assert!(sheet.write(a, 5_i32).is_ok());
3081    }
3082
3083    #[test]
3084    /// Checks immediate reads of a successfully written value.
3085    fn write_read_roundtrip() {
3086        let mut sheet = Sheet::new();
3087        let id = sheet.add_cell(42_i32);
3088        sheet.write(id, 99_i32).unwrap();
3089        assert_eq!(*sheet.read::<i32>(id).unwrap(), 99);
3090    }
3091
3092    #[test]
3093    /// Checks typed-write rejection for an incompatible value.
3094    fn write_wrong_type_returns_type_mismatch() {
3095        let mut sheet = Sheet::new();
3096        let id = sheet.add_cell(0_i32);
3097        assert!(matches!(
3098            sheet.write(id, 1.0_f64),
3099            Err(Error::TypeMismatch { .. })
3100        ));
3101    }
3102
3103    #[test]
3104    /// Checks typed-read rejection for an incompatible requested type.
3105    fn read_wrong_type_returns_type_mismatch() {
3106        let mut sheet = Sheet::new();
3107        let id = sheet.add_cell(0_i32);
3108        assert!(matches!(
3109            sheet.read::<f64>(id),
3110            Err(Error::TypeMismatch { .. })
3111        ));
3112    }
3113
3114    #[test]
3115    /// Checks source/effective agreement without a derived override.
3116    fn source_matches_read_for_a_plain_unshadowed_cell() {
3117        let mut sheet = Sheet::new();
3118        let a = sheet.add_cell(3_i32);
3119        assert_eq!(*sheet.source::<i32>(a).unwrap(), 3);
3120        assert_eq!(*sheet.read::<i32>(a).unwrap(), 3);
3121
3122        sheet.write(a, 8_i32).unwrap();
3123        assert_eq!(*sheet.source::<i32>(a).unwrap(), 8);
3124        assert_eq!(*sheet.read::<i32>(a).unwrap(), 8);
3125    }
3126
3127    #[test]
3128    /// Checks source lookup rejection for an unknown cell.
3129    fn source_returns_invalid_id_for_unknown_cell() {
3130        let sheet = Sheet::new();
3131        assert!(matches!(
3132            sheet.source::<i32>(CellId::default()),
3133            Err(Error::InvalidId)
3134        ));
3135    }
3136
3137    #[test]
3138    /// Checks source lookup rejection for an incompatible requested type.
3139    fn source_wrong_type_returns_type_mismatch() {
3140        let mut sheet = Sheet::new();
3141        let id = sheet.add_cell(0_i32);
3142        assert!(matches!(
3143            sheet.source::<f64>(id),
3144            Err(Error::TypeMismatch { .. })
3145        ));
3146    }
3147
3148    #[test]
3149    /// Checks rejection of a relationship without methods.
3150    fn add_relationship_empty_methods_returns_invalid_method() {
3151        let mut sheet = Sheet::new();
3152        let result = sheet.add_relationship(vec![]);
3153        assert!(matches!(result, Err(Error::InvalidMethod { .. })));
3154        assert!(result.unwrap_err().sites().is_empty());
3155    }
3156
3157    #[test]
3158    /// Checks rejection of a method signature inconsistent with registered cell types.
3159    fn add_relationship_type_mismatch_returns_error() {
3160        let mut sheet = Sheet::new();
3161        let a = sheet.add_cell(0_i32);
3162        let b = sheet.add_cell(0_i32);
3163        // Method declares f64 input but cell holds i32.
3164        let method = Method::from_fn_1_1(a, b, |x: &f64| Ok(*x * 2.0));
3165        let result = sheet.add_relationship(vec![method]);
3166        assert!(matches!(result, Err(Error::TypeMismatch { .. })));
3167        assert_eq!(
3168            result.unwrap_err().sites().first().copied(),
3169            Some(ErrorSite::MethodIndex(0))
3170        );
3171    }
3172
3173    #[test]
3174    /// Checks the producing method's site for a runtime output-type mismatch.
3175    fn execute_plan_type_mismatch_reports_the_method_that_produced_it() {
3176        let mut sheet = Sheet::new();
3177        let a = sheet.add_cell(0_i32);
3178        let b = sheet.add_cell(false); // bool cell
3179        // Declares an i32 output (matching nothing about `b`'s actual bool type at
3180        // add_relationship time -- add_relationship validates output_types against the cell,
3181        // so declare bool here to pass that check, then lie about it at runtime below).
3182        let method = Method::new(
3183            vec![a],
3184            vec![b],
3185            vec![TypeId::of::<i32>()],
3186            vec![TypeId::of::<bool>()],
3187            |args| {
3188                let x = *args[0].downcast_ref::<i32>().unwrap();
3189                // Lies: returns an i32 though output_types declared bool, reproducing the
3190                // execute_plan runtime type-mismatch path (add_relationship can't catch this --
3191                // it only checks the declared TypeId, not what the closure actually returns).
3192                Ok(vec![Box::new(x) as Box<dyn std::any::Any>])
3193            },
3194        );
3195        let rel_id = sheet.add_relationship(vec![method]).unwrap();
3196        let result = sheet.propagate();
3197        assert!(matches!(result, Err(Error::TypeMismatch { .. })));
3198        assert_eq!(
3199            result.unwrap_err().sites().first().copied(),
3200            Some(ErrorSite::Method(rel_id, 0))
3201        );
3202    }
3203
3204    #[test]
3205    /// Checks the producing method's site for a callback failure.
3206    fn execute_plan_method_failed_reports_the_method_that_produced_it() {
3207        let mut sheet = Sheet::new();
3208        let a = sheet.add_cell(0_i32);
3209        let b = sheet.add_cell(0_i32);
3210        let method = Method::from_fn_1_1(a, b, |_: &i32| -> Result<i32, anyhow::Error> {
3211            Err(anyhow::anyhow!("boom"))
3212        });
3213        let rel_id = sheet.add_relationship(vec![method]).unwrap();
3214        let result = sheet.propagate();
3215        assert!(matches!(result, Err(Error::MethodFailed { .. })));
3216        assert_eq!(
3217            result.unwrap_err().sites().first().copied(),
3218            Some(ErrorSite::Method(rel_id, 0))
3219        );
3220    }
3221
3222    #[test]
3223    /// Checks evaluation of a constant-producing zero-input method.
3224    fn add_relationship_zero_input_method_defines_a_fixed_point() {
3225        // A method with no inputs is a valid degenerate case: it always produces the
3226        // same value, independent of every other cell in the sheet.
3227        let mut sheet = Sheet::new();
3228        let b = sheet.add_cell(0_i32);
3229        let method = Method::new(vec![], vec![b], vec![], vec![TypeId::of::<i32>()], |_| {
3230            Ok(vec![Box::new(42_i32)])
3231        });
3232        let rel = sheet.add_relationship(vec![method]).unwrap();
3233        sheet.propagate().unwrap();
3234        assert_eq!(*sheet.read::<i32>(b).unwrap(), 42);
3235        assert!(sheet.is_forced(b));
3236        assert!(sheet.is_relationship_forced(rel));
3237    }
3238
3239    #[test]
3240    /// Checks rejection of a method without outputs.
3241    fn add_relationship_empty_outputs_returns_invalid_method() {
3242        let mut sheet = Sheet::new();
3243        let a = sheet.add_cell(0_i32);
3244        let method = Method::new(
3245            vec![a],
3246            vec![], // no outputs
3247            vec![TypeId::of::<i32>()],
3248            vec![],
3249            |_| Ok(vec![]),
3250        );
3251        let result = sheet.add_relationship(vec![method]);
3252        assert!(matches!(result, Err(Error::InvalidMethod { .. })));
3253        assert_eq!(
3254            result.unwrap_err().sites().first().copied(),
3255            Some(ErrorSite::MethodIndex(0))
3256        );
3257    }
3258
3259    #[test]
3260    /// Checks acceptance of distinct output alternatives sharing one complete cell set.
3261    fn add_relationship_consistent_cell_sets_across_methods_succeeds() {
3262        let mut sheet = Sheet::new();
3263        let a = sheet.add_cell(0_i32);
3264        let b = sheet.add_cell(0_i32);
3265        let c = sheet.add_cell(0_i32);
3266        // Triangle relationship: every method references {a, b, c}.
3267        let result = sheet.add_relationship(vec![
3268            Method::from_fn_2_1([a, b], c, |x: &i32, y: &i32| Ok(x + y)),
3269            Method::from_fn_2_1([a, c], b, |x: &i32, y: &i32| Ok(y - x)),
3270            Method::from_fn_2_1([b, c], a, |x: &i32, y: &i32| Ok(y - x)),
3271        ]);
3272        assert!(result.is_ok());
3273    }
3274
3275    #[test]
3276    /// Checks rejection of alternatives with different complete cell sets.
3277    fn add_relationship_inconsistent_cell_sets_across_methods_returns_mismatched_method_cells() {
3278        let mut sheet = Sheet::new();
3279        let a = sheet.add_cell(0_i32);
3280        let b = sheet.add_cell(0_i32);
3281        let c = sheet.add_cell(0_i32);
3282        // Method 0 references {a, b}; method 1 references {b, c} -- inconsistent.
3283        let result = sheet.add_relationship(vec![
3284            Method::from_fn_1_1(a, b, |v: &i32| Ok(*v)),
3285            Method::from_fn_1_1(b, c, |v: &i32| Ok(*v)),
3286        ]);
3287        assert!(matches!(result, Err(Error::MismatchedMethodCells { .. })));
3288        assert_eq!(
3289            result.unwrap_err().sites().first().copied(),
3290            Some(ErrorSite::MethodIndex(1))
3291        );
3292    }
3293
3294    #[test]
3295    /// Checks rejection of alternatives with identical output sets.
3296    fn add_relationship_duplicate_output_set_across_methods_returns_invalid_method_outputs() {
3297        let mut sheet = Sheet::new();
3298        let a = sheet.add_cell(0_i32);
3299        let b = sheet.add_cell(0_i32);
3300        // Both methods reference {a, b} (so the cell-set-consistency check passes) but
3301        // both output {b} from different inputs -- their output sets are identical,
3302        // which must be rejected.
3303        let result = sheet.add_relationship(vec![
3304            Method::from_fn_2_1([a, b], b, |x: &i32, _y: &i32| Ok(*x)),
3305            Method::from_fn_2_1([a, b], b, |_x: &i32, y: &i32| Ok(*y)),
3306        ]);
3307        assert!(matches!(result, Err(Error::InvalidMethodOutputs { .. })));
3308        assert_eq!(
3309            result.unwrap_err().sites().first().copied(),
3310            Some(ErrorSite::MethodIndex(1))
3311        );
3312    }
3313
3314    #[test]
3315    /// Checks rejection of strictly nested alternative output sets.
3316    fn add_relationship_rejects_strictly_nested_method_outputs() {
3317        let mut sheet = Sheet::new();
3318        let a = sheet.add_cell(0_i32);
3319        let b = sheet.add_cell(0_i32);
3320        let c = sheet.add_cell(0_i32);
3321        // Both methods span {a, b, c}; one outputs {b}, the other outputs {b, c}.
3322        let result = sheet.add_relationship(vec![
3323            Method::from_fn_2_1([a, c], b, |x: &i32, y: &i32| Ok(x + y)),
3324            Method::new(
3325                vec![a],
3326                vec![b, c],
3327                vec![std::any::TypeId::of::<i32>()],
3328                vec![std::any::TypeId::of::<i32>(), std::any::TypeId::of::<i32>()],
3329                |args| {
3330                    let x = *args[0].downcast_ref::<i32>().unwrap();
3331                    Ok(vec![Box::new(x), Box::new(x)])
3332                },
3333            ),
3334        ]);
3335        let err = result.unwrap_err();
3336        assert!(matches!(err, Error::InvalidMethodOutputs { .. }));
3337        let sites = err.sites();
3338        assert_eq!(sites[0], ErrorSite::MethodIndex(1));
3339        assert_eq!(sites[1], ErrorSite::MethodIndex(0));
3340        assert_eq!(sites[2..], [ErrorSite::Cell(b)]);
3341    }
3342
3343    #[test]
3344    /// Checks acceptance of overlapping output alternatives without nesting.
3345    fn add_relationship_accepts_overlapping_non_nested_method_outputs() {
3346        let mut sheet = Sheet::new();
3347        let a = sheet.add_cell(0_i32);
3348        let b = sheet.add_cell(0_i32);
3349        let c = sheet.add_cell(0_i32);
3350        // Both methods span {a, b, c}; outputs overlap at b but neither set nests the other.
3351        let result = sheet.add_relationship(vec![
3352            Method::new(
3353                vec![a, c],
3354                vec![a, b],
3355                vec![std::any::TypeId::of::<i32>(), std::any::TypeId::of::<i32>()],
3356                vec![std::any::TypeId::of::<i32>(), std::any::TypeId::of::<i32>()],
3357                |args| {
3358                    let x = *args[0].downcast_ref::<i32>().unwrap();
3359                    let y = *args[1].downcast_ref::<i32>().unwrap();
3360                    Ok(vec![Box::new(x), Box::new(y)])
3361                },
3362            ),
3363            Method::new(
3364                vec![a, b],
3365                vec![b, c],
3366                vec![std::any::TypeId::of::<i32>(), std::any::TypeId::of::<i32>()],
3367                vec![std::any::TypeId::of::<i32>(), std::any::TypeId::of::<i32>()],
3368                |args| {
3369                    let x = *args[0].downcast_ref::<i32>().unwrap();
3370                    let y = *args[1].downcast_ref::<i32>().unwrap();
3371                    Ok(vec![Box::new(y), Box::new(x)])
3372                },
3373            ),
3374        ]);
3375        assert!(result.is_ok());
3376    }
3377
3378    #[test]
3379    /// Checks distinct identities for separate relationship declarations.
3380    fn add_relationship_returns_distinct_ids() {
3381        let mut sheet = Sheet::new();
3382        let a = sheet.add_cell(0_i32);
3383        let b = sheet.add_cell(0_i32);
3384        let r1 = sheet
3385            .add_relationship(vec![Method::from_fn_1_1(a, b, |x: &i32| Ok(*x))])
3386            .unwrap();
3387        let c = sheet.add_cell(0_i32);
3388        let r2 = sheet
3389            .add_relationship(vec![Method::from_fn_1_1(b, c, |x: &i32| Ok(*x))])
3390            .unwrap();
3391        assert_ne!(r1, r2);
3392    }
3393
3394    #[test]
3395    /// Checks rejection of inconsistent positional cell sets.
3396    fn add_relationship_mismatched_cells_returns_error() {
3397        let mut sheet = Sheet::new();
3398        let a = sheet.add_cell(0_i32);
3399        let b = sheet.add_cell(0_i32);
3400        let c = sheet.add_cell(0_i32);
3401        let d = sheet.add_cell(0_i32);
3402        // Method 0 spans {a, b}; Method 1 spans {c, d} — mismatched cell sets.
3403        let result = sheet.add_relationship(vec![
3404            Method::from_fn_1_1(a, b, |x: &i32| Ok(*x)),
3405            Method::from_fn_1_1(c, d, |x: &i32| Ok(*x)),
3406        ]);
3407        assert!(matches!(result, Err(Error::MismatchedMethodCells { .. })));
3408        assert_eq!(
3409            result.unwrap_err().sites().first().copied(),
3410            Some(ErrorSite::MethodIndex(1))
3411        );
3412    }
3413
3414    #[test]
3415    /// Checks rejection of duplicate alternative output sets.
3416    fn add_relationship_duplicate_output_sets_across_methods_returns_error() {
3417        let mut sheet = Sheet::new();
3418        let a = sheet.add_cell(0_i32);
3419        let b = sheet.add_cell(0_i32);
3420        let c = sheet.add_cell(0_i32);
3421        // Both methods span {a, b, c} and both output {c} — identical output sets.
3422        let result = sheet.add_relationship(vec![
3423            Method::from_fn_2_1([a, b], c, |x: &i32, y: &i32| Ok(*x + *y)),
3424            Method::from_fn_2_1([a, b], c, |x: &i32, y: &i32| Ok(*x - *y)),
3425        ]);
3426        assert!(matches!(result, Err(Error::InvalidMethodOutputs { .. })));
3427        assert_eq!(
3428            result.unwrap_err().sites().first().copied(),
3429            Some(ErrorSite::MethodIndex(1))
3430        );
3431    }
3432
3433    #[test]
3434    /// Checks rejection of a repeated output identity within one method.
3435    fn add_relationship_duplicate_cell_within_own_outputs_returns_error() {
3436        let mut sheet = Sheet::new();
3437        let a = sheet.add_cell(0_i32);
3438        let b = sheet.add_cell(0_i32);
3439        // The method's own outputs list names `b` twice.
3440        let method = Method::new(
3441            vec![a],
3442            vec![b, b],
3443            vec![TypeId::of::<i32>()],
3444            vec![TypeId::of::<i32>(), TypeId::of::<i32>()],
3445            |args| {
3446                let x = args[0].downcast_ref::<i32>().unwrap();
3447                Ok(vec![Box::new(*x), Box::new(*x)])
3448            },
3449        );
3450        let result = sheet.add_relationship(vec![method]);
3451        assert!(matches!(result, Err(Error::InvalidMethodOutputs { .. })));
3452        assert_eq!(
3453            result.unwrap_err().sites().first().copied(),
3454            Some(ErrorSite::MethodIndex(0))
3455        );
3456    }
3457
3458    #[test]
3459    /// Checks both method sites and the differing cell in a cell-set mismatch.
3460    fn mismatched_method_cells_names_both_methods_and_the_differing_cell() {
3461        let mut sheet = Sheet::new();
3462        let a = sheet.add_cell(0_i32);
3463        let b = sheet.add_cell(0_i32);
3464        let c = sheet.add_cell(0_i32);
3465        // method 0 references {a,b}; method 1 references {a,c}: c (and b) diverge.
3466        let err = sheet
3467            .add_relationship(vec![
3468                Method::from_fn_1_1(a, b, |x: &i32| Ok(*x)),
3469                Method::from_fn_1_1(a, c, |x: &i32| Ok(*x)),
3470            ])
3471            .unwrap_err();
3472        let sites = err.sites();
3473        assert_eq!(sites[0], ErrorSite::MethodIndex(1));
3474        assert_eq!(sites[1], ErrorSite::MethodIndex(0));
3475        assert!(sites[2..].contains(&ErrorSite::Cell(c)));
3476    }
3477
3478    #[test]
3479    /// Checks both method sites and the shared cell in a duplicate-output error.
3480    fn duplicate_output_set_across_methods_names_both_methods_and_the_shared_cell() {
3481        let mut sheet = Sheet::new();
3482        let a = sheet.add_cell(0_i32);
3483        let b = sheet.add_cell(0_i32);
3484        // both methods reference {a,b} and both output b.
3485        let err = sheet
3486            .add_relationship(vec![
3487                Method::from_fn_1_1(a, b, |x: &i32| Ok(*x)),
3488                Method::from_fn_1_1(a, b, |x: &i32| Ok(*x + 1)),
3489            ])
3490            .unwrap_err();
3491        let sites = err.sites();
3492        assert_eq!(sites[0], ErrorSite::MethodIndex(1));
3493        assert_eq!(sites[1], ErrorSite::MethodIndex(0));
3494        assert!(sites[2..].contains(&ErrorSite::Cell(b)));
3495    }
3496
3497    #[test]
3498    /// Checks the method and repeated-cell sites for duplicate positional outputs.
3499    fn duplicate_cell_within_own_outputs_names_the_method_and_the_repeated_cell() {
3500        let mut sheet = Sheet::new();
3501        let a = sheet.add_cell(0_i32);
3502        let b = sheet.add_cell(0_i32);
3503        let i32_ty = std::any::TypeId::of::<i32>();
3504        // one method whose outputs name b twice.
3505        let err = sheet
3506            .add_relationship(vec![Method::new(
3507                vec![a],
3508                vec![b, b],
3509                vec![i32_ty],
3510                vec![i32_ty, i32_ty],
3511                |args| {
3512                    let v = *args[0].downcast_ref::<i32>().unwrap();
3513                    Ok(vec![Box::new(v), Box::new(v)])
3514                },
3515            )])
3516            .unwrap_err();
3517        let sites = err.sites();
3518        assert_eq!(sites[0], ErrorSite::MethodIndex(0));
3519        assert!(sites[1..].contains(&ErrorSite::Cell(b)));
3520    }
3521
3522    #[test]
3523    /// Checks deduplication of diagnostic cell sites for repeated outputs.
3524    fn duplicate_cell_repeated_three_times_reports_cell_once() {
3525        let mut sheet = Sheet::new();
3526        let a = sheet.add_cell(0_i32);
3527        let b = sheet.add_cell(0_i32);
3528        let i32_ty = std::any::TypeId::of::<i32>();
3529        // one method whose outputs name b three times.
3530        let err = sheet
3531            .add_relationship(vec![Method::new(
3532                vec![a],
3533                vec![b, b, b],
3534                vec![i32_ty],
3535                vec![i32_ty, i32_ty, i32_ty],
3536                |args| {
3537                    let v = *args[0].downcast_ref::<i32>().unwrap();
3538                    Ok(vec![Box::new(v), Box::new(v), Box::new(v)])
3539                },
3540            )])
3541            .unwrap_err();
3542        let sites = err.sites();
3543        assert_eq!(sites[0], ErrorSite::MethodIndex(0));
3544        let cell_sites: Vec<_> = sites[1..]
3545            .iter()
3546            .filter(|&&s| s == ErrorSite::Cell(b))
3547            .collect();
3548        assert_eq!(
3549            cell_sites.len(),
3550            1,
3551            "expected exactly one Cell(b) site for a triple repeat, got {sites:?}"
3552        );
3553    }
3554
3555    #[test]
3556    /// Checks an empty changed set before propagation.
3557    fn changed_is_empty_before_propagate() {
3558        let sheet = Sheet::new();
3559        assert_eq!(sheet.changed().count(), 0);
3560    }
3561
3562    #[test]
3563    /// Checks changed membership for committed method outputs.
3564    fn changed_after_propagate_contains_method_outputs() {
3565        let mut sheet = Sheet::new();
3566        let a = sheet.add_cell(0_i32);
3567        let b = sheet.add_cell(0_i32);
3568        sheet
3569            .add_relationship(vec![Method::from_fn_1_1(a, b, |x: &i32| Ok(*x * 2))])
3570            .unwrap();
3571        sheet.write(a, 3_i32).unwrap();
3572        sheet.propagate().unwrap();
3573        let changed: Vec<_> = sheet.changed().collect();
3574        assert_eq!(changed, vec![b]);
3575    }
3576
3577    #[test]
3578    /// Checks explicit clearing of changed membership.
3579    fn clear_changed_empties_changed_set() {
3580        let mut sheet = Sheet::new();
3581        let a = sheet.add_cell(0_i32);
3582        let b = sheet.add_cell(0_i32);
3583        sheet
3584            .add_relationship(vec![Method::from_fn_1_1(a, b, |x: &i32| Ok(*x * 2))])
3585            .unwrap();
3586        sheet.write(a, 3_i32).unwrap();
3587        sheet.propagate().unwrap();
3588        sheet.clear_changed();
3589        assert_eq!(sheet.changed().count(), 0);
3590    }
3591
3592    #[test]
3593    /// Checks replacement of previous changed membership after reversing method direction.
3594    fn propagate_clears_previous_changed_set() {
3595        let mut sheet = Sheet::new();
3596        let a = sheet.add_cell(0_i32);
3597        let b = sheet.add_cell(0_i32);
3598        sheet
3599            .add_relationship(vec![
3600                Method::from_fn_1_1(a, b, |x: &i32| Ok(*x)),
3601                Method::from_fn_1_1(b, a, |x: &i32| Ok(*x)),
3602            ])
3603            .unwrap();
3604        sheet.write(a, 3_i32).unwrap();
3605        sheet.propagate().unwrap();
3606        assert_eq!(sheet.changed().collect::<Vec<_>>(), vec![b]);
3607        sheet.write(b, 5_i32).unwrap();
3608        sheet.propagate().unwrap();
3609        let changed: Vec<_> = sheet.changed().collect();
3610        assert_eq!(changed, vec![a]);
3611    }
3612
3613    #[test]
3614    /// Checks enumeration of every live cell identity.
3615    fn cells_returns_all_cell_ids() {
3616        let mut sheet = Sheet::new();
3617        let a = sheet.add_cell(0_i32);
3618        let b = sheet.add_cell(0_i32);
3619        let ids: Vec<_> = sheet.cells().collect();
3620        assert!(ids.contains(&a));
3621        assert!(ids.contains(&b));
3622        assert_eq!(ids.len(), 2);
3623    }
3624
3625    #[test]
3626    /// Checks empty cell enumeration on a new sheet.
3627    fn cells_returns_empty_for_empty_sheet() {
3628        let sheet = Sheet::new();
3629        assert_eq!(sheet.cells().count(), 0);
3630    }
3631
3632    #[test]
3633    /// Checks enumeration of every live relationship identity.
3634    fn relationships_returns_all_relationship_ids() {
3635        let mut sheet = Sheet::new();
3636        let a = sheet.add_cell(0_i32);
3637        let b = sheet.add_cell(0_i32);
3638        let r = sheet
3639            .add_relationship(vec![Method::from_fn_1_1(a, b, |x: &i32| Ok(*x))])
3640            .unwrap();
3641        let ids: Vec<_> = sheet.relationships().collect();
3642        assert_eq!(ids, vec![r]);
3643    }
3644
3645    #[test]
3646    /// Checks empty relationship enumeration on a new sheet.
3647    fn relationships_returns_empty_for_empty_sheet() {
3648        let sheet = Sheet::new();
3649        assert_eq!(sheet.relationships().count(), 0);
3650    }
3651
3652    #[test]
3653    /// Checks a cell's adjacent relationship identities.
3654    fn cell_adj_returns_adjacent_relationships() {
3655        let mut sheet = Sheet::new();
3656        let a = sheet.add_cell(0_i32);
3657        let b = sheet.add_cell(0_i32);
3658        let r = sheet
3659            .add_relationship(vec![Method::from_fn_1_1(a, b, |x: &i32| Ok(*x))])
3660            .unwrap();
3661        assert!(sheet.cell_adj(a).unwrap().contains(&r));
3662        assert!(sheet.cell_adj(b).unwrap().contains(&r));
3663    }
3664
3665    #[test]
3666    /// Checks absent adjacency for an unknown cell.
3667    fn cell_adj_returns_none_for_invalid_id() {
3668        let sheet = Sheet::new();
3669        assert!(sheet.cell_adj(CellId::default()).is_none());
3670    }
3671
3672    #[test]
3673    /// Checks a relationship's complete adjacent cell set.
3674    fn relationship_adj_returns_adjacent_cells() {
3675        let mut sheet = Sheet::new();
3676        let a = sheet.add_cell(0_i32);
3677        let b = sheet.add_cell(0_i32);
3678        let r = sheet
3679            .add_relationship(vec![Method::from_fn_1_1(a, b, |x: &i32| Ok(*x))])
3680            .unwrap();
3681        let adj = sheet.relationship_adj(r).unwrap();
3682        assert!(adj.contains(&a));
3683        assert!(adj.contains(&b));
3684    }
3685
3686    #[test]
3687    /// Checks absent adjacency for an unknown relationship.
3688    fn relationship_adj_returns_none_for_invalid_id() {
3689        let sheet = Sheet::new();
3690        assert!(sheet.relationship_adj(RelationshipId::default()).is_none());
3691    }
3692
3693    #[test]
3694    /// Checks absence of a selected method before propagation.
3695    fn selected_method_returns_none_before_propagate() {
3696        let mut sheet = Sheet::new();
3697        let a = sheet.add_cell(0_i32);
3698        let b = sheet.add_cell(0_i32);
3699        let rel = sheet
3700            .add_relationship(vec![Method::from_fn_1_1(a, b, |x: &i32| Ok(*x))])
3701            .unwrap();
3702        assert!(sheet.selected_method(rel).is_none());
3703    }
3704
3705    #[test]
3706    /// Checks the completed plan's selected method index.
3707    fn selected_method_returns_index_after_propagate() {
3708        let mut sheet = Sheet::new();
3709        let a = sheet.add_cell(0_i32);
3710        let b = sheet.add_cell(0_i32);
3711        let rel = sheet
3712            .add_relationship(vec![Method::from_fn_1_1(a, b, |x: &i32| Ok(*x))])
3713            .unwrap();
3714        // Write to `a` so it has the highest strength and becomes the source,
3715        // making the a → b method eligible.
3716        sheet.write(a, 0_i32).unwrap();
3717        sheet.propagate().unwrap();
3718        assert_eq!(sheet.selected_method(rel), Some(0));
3719    }
3720
3721    #[test]
3722    /// Checks positional input lookup for a live method.
3723    fn method_inputs_returns_inputs_for_valid_method() {
3724        let mut sheet = Sheet::new();
3725        let a = sheet.add_cell(0_i32);
3726        let b = sheet.add_cell(0_i32);
3727        let rel = sheet
3728            .add_relationship(vec![Method::from_fn_1_1(a, b, |x: &i32| Ok(*x))])
3729            .unwrap();
3730        assert_eq!(sheet.method_inputs(rel, 0), Some([a].as_slice()));
3731    }
3732
3733    #[test]
3734    /// Checks positional output lookup for a live method.
3735    fn method_outputs_returns_outputs_for_valid_method() {
3736        let mut sheet = Sheet::new();
3737        let a = sheet.add_cell(0_i32);
3738        let b = sheet.add_cell(0_i32);
3739        let rel = sheet
3740            .add_relationship(vec![Method::from_fn_1_1(a, b, |x: &i32| Ok(*x))])
3741            .unwrap();
3742        assert_eq!(sheet.method_outputs(rel, 0), Some([b].as_slice()));
3743    }
3744
3745    #[test]
3746    /// Checks absent input metadata for an invalid method index.
3747    fn method_inputs_returns_none_for_out_of_bounds_idx() {
3748        let mut sheet = Sheet::new();
3749        let a = sheet.add_cell(0_i32);
3750        let b = sheet.add_cell(0_i32);
3751        let rel = sheet
3752            .add_relationship(vec![Method::from_fn_1_1(a, b, |x: &i32| Ok(*x))])
3753            .unwrap();
3754        assert!(sheet.method_inputs(rel, 99).is_none());
3755    }
3756
3757    #[test]
3758    /// Checks absent output metadata for an invalid method index.
3759    fn method_outputs_returns_none_for_out_of_bounds_idx() {
3760        let mut sheet = Sheet::new();
3761        let a = sheet.add_cell(0_i32);
3762        let b = sheet.add_cell(0_i32);
3763        let rel = sheet
3764            .add_relationship(vec![Method::from_fn_1_1(a, b, |x: &i32| Ok(*x))])
3765            .unwrap();
3766        assert!(sheet.method_outputs(rel, 99).is_none());
3767    }
3768
3769    #[test]
3770    /// Checks absence of completed-plan source classification before propagation.
3771    fn is_source_returns_false_before_propagate() {
3772        let mut sheet = Sheet::new();
3773        let a = sheet.add_cell(0_i32);
3774        let b = sheet.add_cell(0_i32);
3775        sheet
3776            .add_relationship(vec![Method::from_fn_1_1(a, b, |x: &i32| Ok(*x))])
3777            .unwrap();
3778        assert!(!sheet.is_source(a));
3779    }
3780
3781    #[test]
3782    /// Checks source classification of an unclaimed input after propagation.
3783    fn is_source_returns_true_for_input_cell_after_propagate() {
3784        let mut sheet = Sheet::new();
3785        let a = sheet.add_cell(0_i32);
3786        let b = sheet.add_cell(0_i32);
3787        sheet
3788            .add_relationship(vec![Method::from_fn_1_1(a, b, |x: &i32| Ok(*x))])
3789            .unwrap();
3790        // Write to `a` so it has the highest strength and becomes the source.
3791        sheet.write(a, 0_i32).unwrap();
3792        sheet.propagate().unwrap();
3793        assert!(sheet.is_source(a));
3794    }
3795
3796    #[test]
3797    /// Checks non-source classification of a selected method's output.
3798    fn is_source_returns_false_for_output_cell_after_propagate() {
3799        let mut sheet = Sheet::new();
3800        let a = sheet.add_cell(0_i32);
3801        let b = sheet.add_cell(0_i32);
3802        sheet
3803            .add_relationship(vec![Method::from_fn_1_1(a, b, |x: &i32| Ok(*x))])
3804            .unwrap();
3805        // Write to `a` so it has the highest strength and becomes the source.
3806        sheet.write(a, 0_i32).unwrap();
3807        sheet.propagate().unwrap();
3808        assert!(!sheet.is_source(b));
3809    }
3810
3811    // ── Conditional accessor tests ─────────────────────────────────────────
3812
3813    /// Builds a sheet with two keyed branches and returns its conditional identity.
3814    fn sheet_with_two_branch_conditional() -> (Sheet, ConditionalId) {
3815        let mut sheet = Sheet::new();
3816        let a = sheet.add_cell(0_i32);
3817        let b = sheet.add_cell(0_i32);
3818        let p = sheet.add_cell(0_i32);
3819
3820        let rel0 = sheet
3821            .add_relationship(vec![Method::from_fn_1_1(a, b, |v: &i32| Ok(*v))])
3822            .unwrap();
3823        let rel1 = sheet
3824            .add_relationship(vec![Method::from_fn_1_1(b, a, |v: &i32| Ok(*v))])
3825            .unwrap();
3826
3827        let cid = sheet
3828            .add_conditional(
3829                MatchExpr::cell(p),
3830                vec![(vec![0_i32], vec![rel0]), (vec![1_i32], vec![rel1])],
3831                vec![],
3832            )
3833            .unwrap();
3834        (sheet, cid)
3835    }
3836
3837    /// Builds a conditional fixture and identifies its default relationship.
3838    fn sheet_with_default_conditional() -> (Sheet, ConditionalId, RelationshipId) {
3839        let mut sheet = Sheet::new();
3840        let a = sheet.add_cell(0_i32);
3841        let b = sheet.add_cell(0_i32);
3842        let p = sheet.add_cell(99_i32); // no branch matches → default
3843
3844        let rel_default = sheet
3845            .add_relationship(vec![Method::from_fn_1_1(a, b, |v: &i32| Ok(*v))])
3846            .unwrap();
3847
3848        let cid = sheet
3849            .add_conditional::<i32>(MatchExpr::cell(p), vec![], vec![rel_default])
3850            .unwrap();
3851        (sheet, cid, rel_default)
3852    }
3853
3854    #[test]
3855    /// Checks enumeration of a registered conditional identity.
3856    fn conditionals_returns_registered_id() {
3857        let (sheet, cid) = sheet_with_two_branch_conditional();
3858        assert!(sheet.conditionals().any(|id| id == cid));
3859    }
3860
3861    #[test]
3862    /// Checks empty conditional enumeration on a new sheet.
3863    fn conditionals_empty_on_new_sheet() {
3864        let sheet = Sheet::new();
3865        assert_eq!(sheet.conditionals().count(), 0);
3866    }
3867
3868    #[test]
3869    /// Checks the registered conditional's match-cell identity.
3870    fn conditional_match_cells_returns_correct_cell() {
3871        let mut sheet = Sheet::new();
3872        let p = sheet.add_cell(0_i32);
3873        let cid = sheet
3874            .add_conditional::<i32>(MatchExpr::cell(p), vec![], vec![])
3875            .unwrap();
3876        assert_eq!(sheet.conditional_match_cells(cid), Some([p].as_slice()));
3877    }
3878
3879    #[test]
3880    /// Checks absent match inputs for an unknown conditional.
3881    fn conditional_match_cells_returns_none_for_invalid_id() {
3882        let sheet = Sheet::new();
3883        assert_eq!(
3884            sheet.conditional_match_cells(ConditionalId::default()),
3885            None
3886        );
3887    }
3888
3889    #[test]
3890    /// Checks the authored explicit-branch count.
3891    fn conditional_branch_count_returns_correct_count() {
3892        let (sheet, cid) = sheet_with_two_branch_conditional();
3893        assert_eq!(sheet.conditional_branch_count(cid), Some(2));
3894    }
3895
3896    #[test]
3897    /// Checks absent branch counts for an unknown conditional.
3898    fn conditional_branch_count_returns_none_for_invalid_id() {
3899        let sheet = Sheet::new();
3900        assert_eq!(
3901            sheet.conditional_branch_count(ConditionalId::default()),
3902            None
3903        );
3904    }
3905
3906    #[test]
3907    /// Checks the exact authored relationship identities of both explicit branches.
3908    fn conditional_branch_relationships_returns_correct_rels() {
3909        let (sheet, cid) = sheet_with_two_branch_conditional();
3910        let rels0 = sheet.conditional_branch_relationships(cid, 0).unwrap();
3911        let rels1 = sheet.conditional_branch_relationships(cid, 1).unwrap();
3912        let authored: Vec<_> = sheet.relationships().collect();
3913        assert_eq!(rels0, [authored[0]]);
3914        assert_eq!(rels1, [authored[1]]);
3915    }
3916
3917    #[test]
3918    /// Checks absent relationship metadata for an invalid branch index.
3919    fn conditional_branch_relationships_returns_none_for_out_of_bounds() {
3920        let (sheet, cid) = sheet_with_two_branch_conditional();
3921        assert!(sheet.conditional_branch_relationships(cid, 2).is_none());
3922    }
3923
3924    #[test]
3925    /// Checks absent branch relationships for an unknown conditional.
3926    fn conditional_branch_relationships_returns_none_for_invalid_id() {
3927        let sheet = Sheet::new();
3928        assert!(
3929            sheet
3930                .conditional_branch_relationships(ConditionalId::default(), 0)
3931                .is_none()
3932        );
3933    }
3934
3935    #[test]
3936    /// Checks the exact authored default-relationship identity.
3937    fn conditional_default_relationships_returns_correct_rels() {
3938        let (sheet, cid, rel_default) = sheet_with_default_conditional();
3939        let rels = sheet.conditional_default_relationships(cid).unwrap();
3940        assert_eq!(rels, [rel_default]);
3941    }
3942
3943    #[test]
3944    /// Checks empty default membership when no default is declared.
3945    fn conditional_default_relationships_empty_when_no_default() {
3946        let (sheet, cid) = sheet_with_two_branch_conditional();
3947        assert_eq!(sheet.conditional_default_relationships(cid).unwrap(), &[]);
3948    }
3949
3950    #[test]
3951    /// Checks absent default metadata for an unknown conditional.
3952    fn conditional_default_relationships_returns_none_for_invalid_id() {
3953        let sheet = Sheet::new();
3954        assert!(
3955            sheet
3956                .conditional_default_relationships(ConditionalId::default())
3957                .is_none()
3958        );
3959    }
3960
3961    #[test]
3962    /// Checks branch-index lookup for a matching subject.
3963    fn conditional_active_branch_returns_matching_branch_index() {
3964        let (mut sheet, cid) = sheet_with_two_branch_conditional();
3965        let p = sheet.conditional_match_cells(cid).unwrap()[0];
3966        sheet.write(p, 0_i32).unwrap();
3967        assert_eq!(sheet.conditional_active_branch(cid).unwrap(), Some(0));
3968        sheet.write(p, 1_i32).unwrap();
3969        assert_eq!(sheet.conditional_active_branch(cid).unwrap(), Some(1));
3970    }
3971
3972    #[test]
3973    /// Checks absent explicit-branch selection for an unmatched subject.
3974    fn conditional_active_branch_returns_none_when_no_branch_matches() {
3975        let (mut sheet, cid) = sheet_with_two_branch_conditional();
3976        let p = sheet.conditional_match_cells(cid).unwrap()[0];
3977        sheet.write(p, 99_i32).unwrap();
3978        assert_eq!(sheet.conditional_active_branch(cid).unwrap(), None);
3979    }
3980
3981    #[test]
3982    /// Checks absent branch selection for an unknown conditional.
3983    fn conditional_active_branch_returns_none_for_invalid_id() {
3984        let sheet = Sheet::new();
3985        assert_eq!(
3986            sheet
3987                .conditional_active_branch(ConditionalId::default())
3988                .unwrap(),
3989            None
3990        );
3991    }
3992
3993    #[test]
3994    /// Checks filter attachment rejection for an unknown governed cell.
3995    fn add_filter_returns_invalid_id_for_missing_cell() {
3996        let mut sheet = Sheet::new();
3997        let result = sheet.add_filter(CellId::default(), Filter::from_fn_0(|x: &i32| Ok(*x)));
3998        assert!(matches!(result, Err(Error::InvalidId)));
3999    }
4000
4001    #[test]
4002    /// Checks that attachment alone preserves the cell's current value.
4003    fn add_filter_does_not_change_the_cells_current_value() {
4004        let mut sheet = Sheet::new();
4005        let a = sheet.add_cell(500_i32);
4006        sheet
4007            .add_filter(a, Filter::from_fn_0(|x: &i32| Ok((*x).clamp(0, 100))))
4008            .unwrap();
4009        // add_filter never evaluates the function against the current value: the raw
4010        // out-of-range value survives until the next propagate().
4011        assert_eq!(*sheet.read::<i32>(a).unwrap(), 500);
4012    }
4013
4014    #[test]
4015    /// Checks conformance of the initial value at propagation rather than attachment.
4016    fn propagate_after_add_filter_conforms_the_initial_value() {
4017        let mut sheet = Sheet::new();
4018        let a = sheet.add_cell(500_i32);
4019        sheet
4020            .add_filter(a, Filter::from_fn_0(|x: &i32| Ok((*x).clamp(0, 100))))
4021            .unwrap();
4022        assert_eq!(*sheet.read::<i32>(a).unwrap(), 500);
4023        sheet.propagate().unwrap();
4024        // `a` has no filter args and belongs to no relationship — this is the ordinary
4025        // first-round case of Task 1's fix, not a special "cold start" path.
4026        assert_eq!(*sheet.read::<i32>(a).unwrap(), 100);
4027    }
4028
4029    #[test]
4030    /// Checks rejection of a second filter on the same cell.
4031    fn add_filter_returns_invalid_filter_when_cell_already_has_a_filter() {
4032        let mut sheet = Sheet::new();
4033        let a = sheet.add_cell(5_i32);
4034        sheet
4035            .add_filter(a, Filter::from_fn_0(|x: &i32| Ok(*x)))
4036            .unwrap();
4037        let result = sheet.add_filter(a, Filter::from_fn_0(|x: &i32| Ok(*x)));
4038        assert!(matches!(result, Err(Error::InvalidFilter)));
4039    }
4040
4041    #[test]
4042    /// Checks rejection of a filter declaring the wrong governed-value type.
4043    fn add_filter_returns_invalid_filter_for_mismatched_value_type() {
4044        let mut sheet = Sheet::new();
4045        let a = sheet.add_cell(5_i32);
4046        let result = sheet.add_filter(a, Filter::from_fn_0(|x: &f64| Ok(*x)));
4047        assert!(matches!(result, Err(Error::InvalidFilter)));
4048    }
4049
4050    #[test]
4051    /// Checks rejection of a filter's direct dynamic self-dependency.
4052    fn add_filter_returns_invalid_filter_when_args_name_the_filtered_cell_itself() {
4053        let mut sheet = Sheet::new();
4054        let a = sheet.add_cell(5_i32);
4055        let result = sheet.add_filter(
4056            a,
4057            Filter::from_fn_1(a, |x: &i32, bound: &i32| Ok((*x).min(*bound))),
4058        );
4059        assert!(matches!(result, Err(Error::InvalidFilter)));
4060    }
4061
4062    #[test]
4063    /// Checks filter attachment rejection for an unknown argument.
4064    fn add_filter_returns_invalid_id_for_missing_arg_cell() {
4065        let mut sheet = Sheet::new();
4066        let a = sheet.add_cell(5_i32);
4067        let result = sheet.add_filter(
4068            a,
4069            Filter::from_fn_1(CellId::default(), |x: &i32, bound: &i32| {
4070                Ok((*x).min(*bound))
4071            }),
4072        );
4073        assert!(matches!(result, Err(Error::InvalidId)));
4074    }
4075
4076    #[test]
4077    /// Checks filter attachment rejection for an incompatible argument type.
4078    fn add_filter_returns_type_mismatch_for_wrong_arg_cell_type() {
4079        let mut sheet = Sheet::new();
4080        let a = sheet.add_cell(5_i32);
4081        let bound = sheet.add_cell(1.0_f64); // wrong type: filter declares i32
4082        let result = sheet.add_filter(
4083            a,
4084            Filter::from_fn_1(bound, |x: &i32, bound: &i32| Ok((*x).min(*bound))),
4085        );
4086        assert!(matches!(result, Err(Error::TypeMismatch { .. })));
4087    }
4088
4089    #[test]
4090    /// Checks filter attachment to a source-only cell.
4091    fn add_filter_succeeds_on_a_source_kind_cell() {
4092        let mut sheet = Sheet::new();
4093        let a = sheet.add_source(5_i32);
4094        assert!(
4095            sheet
4096                .add_filter(a, Filter::from_fn_0(|x: &i32| Ok((*x).clamp(0, 10))))
4097                .is_ok()
4098        );
4099    }
4100
4101    #[test]
4102    /// Checks that both dynamic bounds affect a binary filter's conformed value.
4103    fn from_fn_2_conforms_values_through_sheet_using_both_dynamic_arguments() {
4104        let mut sheet = Sheet::new();
4105        let a = sheet.add_cell(500_i32);
4106        let lo = sheet.add_cell(0_i32);
4107        let hi = sheet.add_cell(100_i32);
4108        sheet
4109            .add_filter(
4110                a,
4111                Filter::from_fn_2([lo, hi], |x: &i32, lo: &i32, hi: &i32| {
4112                    Ok((*x).clamp(*lo, *hi))
4113                }),
4114            )
4115            .unwrap();
4116        sheet.propagate().unwrap();
4117        assert_eq!(*sheet.read::<i32>(a).unwrap(), 100);
4118
4119        sheet.write(a, -10_i32).unwrap();
4120        sheet.propagate().unwrap();
4121        assert_eq!(*sheet.read::<i32>(a).unwrap(), 0);
4122    }
4123
4124    #[test]
4125    /// Checks direct source/effective publication by an unfiltered write.
4126    fn write_without_a_filter_behaves_exactly_as_before() {
4127        let mut sheet = Sheet::new();
4128        let a = sheet.add_cell(0_i32);
4129        sheet.write(a, 42_i32).unwrap();
4130        assert_eq!(*sheet.read::<i32>(a).unwrap(), 42);
4131    }
4132
4133    #[test]
4134    /// Checks preservation of the raw aspiration across deferred filtering.
4135    fn write_leaves_the_raw_value_in_source_until_propagate_conforms_it() {
4136        let mut sheet = Sheet::new();
4137        let a = sheet.add_cell(0_i32);
4138        sheet
4139            .add_filter(a, Filter::from_fn_0(|x: &i32| Ok((*x).clamp(0, 100))))
4140            .unwrap();
4141        sheet.write(a, 500_i32).unwrap();
4142        // write() no longer runs the filter: the raw value stands until propagate().
4143        assert_eq!(*sheet.read::<i32>(a).unwrap(), 500);
4144        sheet.propagate().unwrap();
4145        assert_eq!(*sheet.read::<i32>(a).unwrap(), 100);
4146    }
4147
4148    #[test]
4149    /// Checks absence of diagnostics for a conforming derived value.
4150    fn propagate_reports_no_violation_when_a_derived_value_conforms() {
4151        let mut sheet = Sheet::new();
4152        let a = sheet.add_cell(10_i32);
4153        let b = sheet.add_cell(0_i32);
4154        sheet
4155            .add_filter(b, Filter::from_fn_0(|x: &i32| Ok((*x).clamp(0, 100))))
4156            .unwrap();
4157        sheet
4158            .add_relationship(vec![Method::from_fn_1_1(a, b, |x: &i32| Ok(*x))])
4159            .unwrap();
4160        sheet.propagate().unwrap();
4161        assert_eq!(*sheet.read::<i32>(b).unwrap(), 10);
4162        assert!(sheet.last_filter_violations.is_empty());
4163    }
4164
4165    #[test]
4166    /// Checks a soft nonconformance diagnostic without rewriting a derived value.
4167    fn propagate_reports_not_conformed_when_a_derived_value_violates_its_filter() {
4168        let mut sheet = Sheet::new();
4169        let a = sheet.add_cell(60_i32);
4170        let b = sheet.add_cell(0_i32);
4171        sheet
4172            .add_filter(b, Filter::from_fn_0(|x: &i32| Ok((*x).clamp(0, 100))))
4173            .unwrap();
4174        sheet
4175            .add_relationship(vec![Method::from_fn_1_1(a, b, |x: &i32| Ok(*x * 2))])
4176            .unwrap();
4177        sheet.propagate().unwrap();
4178        // 60 * 2 = 120, clamp(0, 100) => 100 != 120.
4179        assert_eq!(*sheet.read::<i32>(b).unwrap(), 120);
4180        assert!(matches!(
4181            sheet.last_filter_violations.get(&b),
4182            Some(FilterViolation::NotConformed)
4183        ));
4184    }
4185
4186    #[test]
4187    /// Checks a soft callback-failure diagnostic for a derived value.
4188    fn propagate_reports_failed_when_the_filter_errors_on_a_derived_value() {
4189        let mut sheet = Sheet::new();
4190        let a = sheet.add_cell(1_i32);
4191        let b = sheet.add_cell(0_i32);
4192        // Accept exactly 0 (b's initial value) so this filter's shape is exercised
4193        // only by the relationship's derived value (1, copied from `a`), not by
4194        // anything add_filter itself does — add_filter no longer evaluates a filter's
4195        // function at all.
4196        sheet
4197            .add_filter(
4198                b,
4199                Filter::from_fn_0(|x: &i32| {
4200                    if *x == 0 {
4201                        Ok(*x)
4202                    } else {
4203                        Err(anyhow::anyhow!("cannot conform"))
4204                    }
4205                }),
4206            )
4207            .unwrap();
4208        sheet
4209            .add_relationship(vec![Method::from_fn_1_1(a, b, |x: &i32| Ok(*x))])
4210            .unwrap();
4211        // propagate() must not abort even though the filter errors.
4212        sheet.propagate().unwrap();
4213        assert!(matches!(
4214            sheet.last_filter_violations.get(&b),
4215            Some(FilterViolation::Failed(_))
4216        ));
4217    }
4218
4219    #[test]
4220    /// Checks a soft type-failure diagnostic for a malformed derived-value filter result.
4221    fn propagate_reports_failed_when_the_filter_returns_the_wrong_type_on_a_derived_value() {
4222        let mut sheet = Sheet::new();
4223        let a = sheet.add_cell(1_i32);
4224        let b = sheet.add_cell(0_i32);
4225        // Returns a `f64` for a nonzero derived value, tripping
4226        // propagate()'s diagnostic-phase defensive check once `a`'s value (1) is
4227        // copied into `b` this round.
4228        let filter = Filter::new(TypeId::of::<i32>(), vec![], vec![], |value, _args| {
4229            let v = *value.downcast_ref::<i32>().unwrap();
4230            if v == 0 {
4231                Ok(Box::new(v) as Box<dyn Any>)
4232            } else {
4233                Ok(Box::new(1.5_f64) as Box<dyn Any>)
4234            }
4235        });
4236        sheet.add_filter(b, filter).unwrap();
4237        sheet
4238            .add_relationship(vec![Method::from_fn_1_1(a, b, |x: &i32| Ok(*x))])
4239            .unwrap();
4240        // propagate() must not abort even though the filter's function returns the
4241        // wrong type.
4242        sheet.propagate().unwrap();
4243        assert!(matches!(
4244            sheet.filter_violation(b),
4245            Some(FilterViolation::Failed(_))
4246        ));
4247    }
4248
4249    #[test]
4250    /// Checks absence of nonconformance diagnostics for a successfully conformed source.
4251    fn propagate_never_flags_a_filtered_cell_that_stayed_a_plain_source() {
4252        let mut sheet = Sheet::new();
4253        let a = sheet.add_cell(60_i32);
4254        sheet
4255            .add_filter(a, Filter::from_fn_0(|x: &i32| Ok((*x).clamp(0, 100))))
4256            .unwrap();
4257        sheet.propagate().unwrap();
4258        assert!(sheet.last_filter_violations.is_empty());
4259    }
4260
4261    #[test]
4262    /// Checks reclamping of a source after its dynamic argument changes.
4263    fn propagate_reclamps_a_filtered_source_cell_when_its_argument_changes() {
4264        // Issue #132's exact repro.
4265        let mut sheet = Sheet::new();
4266        let a = sheet.add_cell(50_i32);
4267        let bound = sheet.add_cell(100_i32);
4268        sheet
4269            .add_filter(
4270                a,
4271                Filter::from_fn_1(bound, |v: &i32, b: &i32| Ok((*v).min(*b))),
4272            )
4273            .unwrap();
4274        sheet.write(bound, 10_i32).unwrap();
4275        sheet.propagate().unwrap();
4276        assert_eq!(*sheet.read::<i32>(a).unwrap(), 10);
4277    }
4278
4279    #[test]
4280    /// Checks that downstream methods consume the settled conformed source.
4281    fn propagate_reclamps_before_a_relationship_consumes_the_reclamped_value() {
4282        // The inequality.adm2-shaped case: a and b are linked by a two-method mutual
4283        // relationship (b := min(a, b); a := max(a, b)); a is the currently-source cell
4284        // of the pair and is filtered against a bound that just shrank. b (derived) must
4285        // reflect the corrected a, not the pre-reclamp one, within a single propagate().
4286        //
4287        // b is created before a so that a (created later) outranks b in strength —
4288        // release::resolve keeps the higher-strength cell a source (see
4289        // release::tests::strength_prefers_the_higher_strength_cell_as_source) — making a
4290        // the source and b the derived cell of the pair, as this test needs.
4291        let mut sheet = Sheet::new();
4292        let b = sheet.add_cell(20_i32);
4293        let a = sheet.add_cell(50_i32);
4294        let bound = sheet.add_cell(100_i32);
4295        sheet
4296            .add_filter(
4297                a,
4298                Filter::from_fn_1(bound, |v: &i32, bnd: &i32| Ok((*v).min(*bnd))),
4299            )
4300            .unwrap();
4301        sheet
4302            .add_relationship(vec![
4303                Method::from_fn_2_1([a, b], b, |x: &i32, y: &i32| Ok((*x).min(*y))),
4304                Method::from_fn_2_1([a, b], a, |x: &i32, y: &i32| Ok((*x).max(*y))),
4305            ])
4306            .unwrap();
4307        sheet.propagate().unwrap();
4308
4309        sheet.write(bound, 5_i32).unwrap();
4310        sheet.propagate().unwrap();
4311
4312        // a reclamps to min(50, 5) = 5; b's method (a.min(b)) then reads the reclamped
4313        // a, not the stale 50.
4314        assert_eq!(*sheet.read::<i32>(a).unwrap(), 5);
4315        assert_eq!(*sheet.read::<i32>(b).unwrap(), 5);
4316    }
4317
4318    #[test]
4319    /// Checks restoration of the stored aspiration when a dynamic bound loosens.
4320    fn filtered_source_cell_springs_back_to_its_original_value_when_a_bound_loosens() {
4321        let mut sheet = Sheet::new();
4322        let a = sheet.add_cell(50_i32);
4323        let bound = sheet.add_cell(100_i32);
4324        sheet
4325            .add_filter(
4326                a,
4327                Filter::from_fn_1(bound, |v: &i32, b: &i32| Ok((*v).min(*b))),
4328            )
4329            .unwrap();
4330
4331        sheet.write(bound, 10_i32).unwrap();
4332        sheet.propagate().unwrap();
4333        assert_eq!(*sheet.read::<i32>(a).unwrap(), 10);
4334
4335        sheet.write(bound, 100_i32).unwrap();
4336        sheet.propagate().unwrap();
4337        // a's original 50 must survive in `source` across the whole round-trip: it
4338        // springs back once the bound loosens again, rather than staying stuck at the
4339        // intermediate clamp.
4340        assert_eq!(*sheet.read::<i32>(a).unwrap(), 50);
4341    }
4342
4343    #[test]
4344    /// Checks a soft diagnostic for a malformed successful source-filter result.
4345    fn filter_reclamp_records_failed_violation_when_the_filters_function_returns_the_wrong_type() {
4346        let mut sheet = Sheet::new();
4347        let a = sheet.add_cell(5_i32);
4348        let trigger = sheet.add_cell(0_i32);
4349        // Returns an f64 when trigger becomes non-zero,
4350        // tripping FilterReclamp's check once `trigger` is updated.
4351        let filter = Filter::new(
4352            TypeId::of::<i32>(),
4353            vec![trigger],
4354            vec![TypeId::of::<i32>()],
4355            |value, args| {
4356                let v = *value.downcast_ref::<i32>().unwrap();
4357                let t = *args[0].downcast_ref::<i32>().unwrap();
4358                if t == 0 {
4359                    Ok(Box::new(v) as Box<dyn Any>)
4360                } else {
4361                    Ok(Box::new(1.5_f64) as Box<dyn Any>)
4362                }
4363            },
4364        );
4365        sheet.add_filter(a, filter).unwrap();
4366
4367        // trigger changes, causing reclamp where the filter returns wrong type
4368        sheet.write(trigger, 1_i32).unwrap();
4369        sheet.propagate().unwrap();
4370
4371        assert!(matches!(
4372            sheet.filter_violation(a),
4373            Some(FilterViolation::Failed(_))
4374        ));
4375        // The wrong-type result is discarded: the cell's stored value is unchanged.
4376        assert_eq!(*sheet.read::<i32>(a).unwrap(), 5);
4377    }
4378
4379    #[test]
4380    /// Checks that a failed reclamp preserves the cell and does not abort propagation.
4381    fn filter_reclamp_failure_is_recorded_without_aborting_propagate_or_changing_the_cell() {
4382        let mut sheet = Sheet::new();
4383        let a = sheet.add_cell(5_i32);
4384        let bound = sheet.add_cell(100_i32);
4385        // Accept anything up to `bound` (a's initial 5 is within bound's initial 100);
4386        // the write to `bound` below is what trips the filter's next live reclamp.
4387        sheet
4388            .add_filter(
4389                a,
4390                Filter::from_fn_1(bound, |v: &i32, b: &i32| {
4391                    if *v <= *b {
4392                        Ok(*v)
4393                    } else {
4394                        Err(anyhow::anyhow!("cannot conform"))
4395                    }
4396                }),
4397            )
4398            .unwrap();
4399        sheet.write(bound, 0_i32).unwrap();
4400
4401        sheet.propagate().unwrap();
4402
4403        assert!(matches!(
4404            sheet.filter_violation(a),
4405            Some(FilterViolation::Failed(_))
4406        ));
4407        // Rejected reclamp: the cell's stored value is left completely unchanged.
4408        assert_eq!(*sheet.read::<i32>(a).unwrap(), 5);
4409    }
4410
4411    #[test]
4412    /// Checks positional lookup of a filter's dynamic argument identities.
4413    fn filter_args_returns_the_filters_argument_cells() {
4414        let mut sheet = Sheet::new();
4415        let a = sheet.add_cell(5_i32);
4416        let bound = sheet.add_cell(10_i32);
4417        sheet
4418            .add_filter(
4419                a,
4420                Filter::from_fn_1(bound, |x: &i32, bound: &i32| Ok((*x).min(*bound))),
4421            )
4422            .unwrap();
4423        assert_eq!(sheet.filter_args(a), Some(&[bound][..]));
4424    }
4425
4426    #[test]
4427    /// Checks absent argument metadata on an unfiltered cell.
4428    fn filter_args_returns_none_for_a_cell_with_no_filter() {
4429        let mut sheet = Sheet::new();
4430        let a = sheet.add_cell(5_i32);
4431        assert_eq!(sheet.filter_args(a), None);
4432    }
4433
4434    #[test]
4435    /// Checks absent filter arguments for an unknown cell.
4436    fn filter_args_returns_none_for_an_invalid_cell() {
4437        let sheet = Sheet::new();
4438        assert_eq!(sheet.filter_args(CellId::default()), None);
4439    }
4440
4441    #[test]
4442    /// Checks absence of filter diagnostics before propagation.
4443    fn filter_violation_returns_none_before_any_propagate() {
4444        let mut sheet = Sheet::new();
4445        let cell = sheet.add_cell(100_i32);
4446        sheet
4447            .add_filter(
4448                cell,
4449                Filter::from_fn_0(|value: &i32| Ok((*value).clamp(0, 10))),
4450            )
4451            .unwrap();
4452        assert!(sheet.filter_violation(cell).is_none());
4453        assert!(sheet.filter_violation(CellId::default()).is_none());
4454    }
4455
4456    #[test]
4457    /// Checks enumeration of a currently nonconforming filtered cell.
4458    fn filter_violated_cells_reports_a_currently_violated_filter() {
4459        let mut sheet = Sheet::new();
4460        let a = sheet.add_cell(60_i32);
4461        let b = sheet.add_cell(0_i32);
4462        sheet
4463            .add_filter(b, Filter::from_fn_0(|x: &i32| Ok((*x).clamp(0, 100))))
4464            .unwrap();
4465        sheet
4466            .add_relationship(vec![Method::from_fn_1_1(a, b, |x: &i32| Ok(*x * 2))])
4467            .unwrap();
4468        sheet.propagate().unwrap();
4469        assert!(sheet.filter_violated_cells().any(|id| id == b));
4470        assert!(matches!(
4471            sheet.filter_violation(b),
4472            Some(FilterViolation::NotConformed)
4473        ));
4474    }
4475
4476    #[test]
4477    /// Checks an empty diagnostic-contributor set when all filters conform.
4478    fn filter_violation_cells_is_empty_when_nothing_is_violated() {
4479        let mut sheet = Sheet::new();
4480        let a = sheet.add_cell(10_i32);
4481        sheet
4482            .add_filter(a, Filter::from_fn_0(|x: &i32| Ok((*x).clamp(0, 100))))
4483            .unwrap();
4484        sheet.propagate().unwrap();
4485        assert!(sheet.filter_violation_cells().is_empty());
4486    }
4487
4488    #[test]
4489    /// Checks root contributors of a nonconforming derived value.
4490    fn filter_violation_cells_includes_root_causes_of_a_violation() {
4491        let mut sheet = Sheet::new();
4492        let a = sheet.add_cell(60_i32);
4493        let bound = sheet.add_cell(100_i32);
4494        let b = sheet.add_cell(0_i32);
4495        sheet
4496            .add_filter(
4497                b,
4498                Filter::from_fn_1(bound, |x: &i32, bound: &i32| Ok((*x).min(*bound))),
4499            )
4500            .unwrap();
4501        sheet
4502            .add_relationship(vec![Method::from_fn_1_1(a, b, |x: &i32| Ok(*x * 2))])
4503            .unwrap();
4504        sheet.propagate().unwrap();
4505        let violation_cells = sheet.filter_violation_cells();
4506        // `b` is forced (its relationship has only one method), so — mirroring
4507        // `contributing_cells`'s existing semantics — it is `a` and `bound` that
4508        // appear as the upstream root causes, not `b` itself. `b`'s own membership
4509        // is already answered by `filter_violated_cells()`, tested separately above.
4510        assert!(violation_cells.contains(&a));
4511        assert!(violation_cells.contains(&bound));
4512    }
4513
4514    #[test]
4515    /// Checks root contributors of a failed filter diagnostic.
4516    fn filter_violation_cells_includes_root_causes_of_a_failed_violation() {
4517        let mut sheet = Sheet::new();
4518        let a = sheet.add_cell(1_i32);
4519        let bound = sheet.add_cell(100_i32);
4520        let b = sheet.add_cell(0_i32);
4521        // Errors on a nonzero input so the relationship's derived value (copied from `a`) trips a
4522        // `Failed` violation instead of `NotConformed`.
4523        sheet
4524            .add_filter(
4525                b,
4526                Filter::from_fn_1(bound, |x: &i32, _bound: &i32| {
4527                    if *x == 0 {
4528                        Ok(*x)
4529                    } else {
4530                        Err(anyhow::anyhow!("cannot conform"))
4531                    }
4532                }),
4533            )
4534            .unwrap();
4535        sheet
4536            .add_relationship(vec![Method::from_fn_1_1(a, b, |x: &i32| Ok(*x))])
4537            .unwrap();
4538        sheet.propagate().unwrap();
4539        assert!(matches!(
4540            sheet.filter_violation(b),
4541            Some(FilterViolation::Failed(_))
4542        ));
4543        let violation_cells = sheet.filter_violation_cells();
4544        // Mirroring the `NotConformed` case above: `b` is forced, so `a` and `bound`
4545        // are the upstream root causes, not `b` itself.
4546        assert!(violation_cells.contains(&a));
4547        assert!(violation_cells.contains(&bound));
4548    }
4549
4550    #[test]
4551    /// Checks reverse lookup of filter-argument dependents.
4552    fn filter_dependents_returns_the_cells_whose_filter_references_this_one() {
4553        let mut sheet = Sheet::new();
4554        let bound = sheet.add_cell(10_i32);
4555        let a = sheet.add_cell(5_i32);
4556        sheet
4557            .add_filter(
4558                a,
4559                Filter::from_fn_1(bound, |v: &i32, b: &i32| Ok((*v).min(*b))),
4560            )
4561            .unwrap();
4562        assert_eq!(sheet.filter_dependents(bound), &[a]);
4563    }
4564
4565    #[test]
4566    /// Checks empty reverse-filter membership for an unreferenced cell.
4567    fn filter_dependents_is_empty_for_a_cell_no_filter_references() {
4568        let mut sheet = Sheet::new();
4569        let bound = sheet.add_cell(10_i32);
4570        let a = sheet.add_cell(5_i32);
4571        sheet
4572            .add_filter(a, Filter::from_fn_0(|v: &i32| Ok((*v).clamp(0, 100))))
4573            .unwrap();
4574        assert!(sheet.filter_dependents(bound).is_empty());
4575    }
4576
4577    #[test]
4578    /// Checks empty reverse-filter membership for an unknown cell.
4579    fn filter_dependents_is_empty_for_an_invalid_cell() {
4580        let sheet = Sheet::new();
4581        assert!(sheet.filter_dependents(CellId::default()).is_empty());
4582    }
4583
4584    #[test]
4585    /// Checks complete reverse membership when several filters share an argument.
4586    fn filter_dependents_aggregates_multiple_dependents_of_the_same_argument() {
4587        let mut sheet = Sheet::new();
4588        let bound = sheet.add_cell(10_i32);
4589        let a = sheet.add_cell(5_i32);
4590        let b = sheet.add_cell(5_i32);
4591        sheet
4592            .add_filter(
4593                a,
4594                Filter::from_fn_1(bound, |v: &i32, bd: &i32| Ok((*v).min(*bd))),
4595            )
4596            .unwrap();
4597        sheet
4598            .add_filter(
4599                b,
4600                Filter::from_fn_1(bound, |v: &i32, bd: &i32| Ok((*v).min(*bd))),
4601            )
4602            .unwrap();
4603        let dependents = sheet.filter_dependents(bound);
4604        assert_eq!(dependents.len(), 2);
4605        assert!(dependents.contains(&a));
4606        assert!(dependents.contains(&b));
4607    }
4608
4609    #[test]
4610    /// Checks absent presentation metadata on an unfiltered cell.
4611    fn filter_kind_returns_none_for_a_cell_with_no_filter() {
4612        let mut sheet = Sheet::new();
4613        let a = sheet.add_cell(0_i32);
4614        assert!(sheet.filter_kind(a).is_none());
4615    }
4616
4617    #[test]
4618    /// Checks opaque metadata for an ordinary filter.
4619    fn filter_kind_returns_opaque_for_a_plain_filter() {
4620        let mut sheet = Sheet::new();
4621        let a = sheet.add_cell(0_i32);
4622        sheet
4623            .add_filter(a, Filter::from_fn_0(|x: &i32| Ok(*x)))
4624            .unwrap();
4625        assert!(matches!(sheet.filter_kind(a), Some(FilterKind::Opaque)));
4626    }
4627
4628    #[test]
4629    /// Checks range metadata for a range filter.
4630    fn filter_kind_returns_range_for_a_range_filter() {
4631        let mut sheet = Sheet::new();
4632        let a = sheet.add_cell(0_i32);
4633        let filter = Filter::range(
4634            TypeId::of::<i32>(),
4635            vec![],
4636            vec![],
4637            |value, _args| Ok(Box::new(*value.downcast_ref::<i32>().unwrap()) as Box<dyn Any>),
4638            |_args| {
4639                Some((
4640                    Box::new(0i32) as Box<dyn Any>,
4641                    Box::new(100i32) as Box<dyn Any>,
4642                ))
4643            },
4644        );
4645        sheet.add_filter(a, filter).unwrap();
4646        assert!(matches!(
4647            sheet.filter_kind(a),
4648            Some(FilterKind::Range { .. })
4649        ));
4650    }
4651
4652    #[test]
4653    /// Checks live range-bound values read from dynamic argument cells.
4654    fn filter_range_returns_live_bounds_from_argument_cells() {
4655        let mut sheet = Sheet::new();
4656        let a = sheet.add_cell(0_i32);
4657        let lo = sheet.add_cell(0_i32);
4658        let hi = sheet.add_cell(100_i32);
4659        let filter = Filter::range(
4660            TypeId::of::<i32>(),
4661            vec![lo, hi],
4662            vec![TypeId::of::<i32>(), TypeId::of::<i32>()],
4663            |value, args| {
4664                let v = *value.downcast_ref::<i32>().unwrap();
4665                let lo = *args[0].downcast_ref::<i32>().unwrap();
4666                let hi = *args[1].downcast_ref::<i32>().unwrap();
4667                Ok(Box::new(v.clamp(lo, hi)) as Box<dyn Any>)
4668            },
4669            |args| {
4670                Some((
4671                    Box::new(*args[0].downcast_ref::<i32>().unwrap()) as Box<dyn Any>,
4672                    Box::new(*args[1].downcast_ref::<i32>().unwrap()) as Box<dyn Any>,
4673                ))
4674            },
4675        );
4676        sheet.add_filter(a, filter).unwrap();
4677        assert_eq!(sheet.filter_range::<i32>(a), Some((0, 100)));
4678        sheet.write(hi, 10_i32).unwrap();
4679        assert_eq!(sheet.filter_range::<i32>(a), Some((0, 10)));
4680    }
4681
4682    #[test]
4683    /// Checks range introspection of a relationship-derived bound.
4684    fn filter_range_reflects_a_bound_derived_by_a_relationship_not_just_a_direct_write() {
4685        // `hi` isn't itself written — its value is derived from `hi_source` via a relationship —
4686        // exercising `filter_range`'s use of `effective()` (which sees a relationship's derived
4687        // override), not just `source`.
4688        let mut sheet = Sheet::new();
4689        let a = sheet.add_cell(0_i32);
4690        let lo = sheet.add_cell(0_i32);
4691        let hi = sheet.add_cell(100_i32);
4692        let hi_source = sheet.add_cell(100_i32);
4693        sheet
4694            .add_relationship(vec![Method::from_fn_1_1(hi_source, hi, |v: &i32| Ok(*v))])
4695            .unwrap();
4696        let filter = Filter::range(
4697            TypeId::of::<i32>(),
4698            vec![lo, hi],
4699            vec![TypeId::of::<i32>(), TypeId::of::<i32>()],
4700            |value, args| {
4701                let v = *value.downcast_ref::<i32>().unwrap();
4702                let lo = *args[0].downcast_ref::<i32>().unwrap();
4703                let hi = *args[1].downcast_ref::<i32>().unwrap();
4704                Ok(Box::new(v.clamp(lo, hi)) as Box<dyn Any>)
4705            },
4706            |args| {
4707                Some((
4708                    Box::new(*args[0].downcast_ref::<i32>().unwrap()) as Box<dyn Any>,
4709                    Box::new(*args[1].downcast_ref::<i32>().unwrap()) as Box<dyn Any>,
4710                ))
4711            },
4712        );
4713        sheet.add_filter(a, filter).unwrap();
4714        sheet.write(hi_source, 20_i32).unwrap();
4715        sheet.propagate().unwrap();
4716        assert_eq!(sheet.filter_range::<i32>(a), Some((0, 20)));
4717    }
4718
4719    #[test]
4720    /// Checks absence of range bounds for an opaque filter.
4721    fn filter_range_returns_none_for_an_opaque_filter() {
4722        let mut sheet = Sheet::new();
4723        let a = sheet.add_cell(0_i32);
4724        sheet
4725            .add_filter(a, Filter::from_fn_0(|x: &i32| Ok(*x)))
4726            .unwrap();
4727        assert!(sheet.filter_range::<i32>(a).is_none());
4728    }
4729
4730    #[test]
4731    /// Attaches each check independently and returns IDs in registration order.
4732    fn add_requirement_returns_distinct_ids_on_same_cell() {
4733        let mut sheet = Sheet::new();
4734        let a = sheet.add_cell(5_i32);
4735        let first = sheet
4736            .add_requirement(a, Requirement::from_fn_1(a, |x: &i32| Ok(*x > 0)))
4737            .unwrap();
4738        let result = sheet.add_requirement(a, Requirement::from_fn_1(a, |x: &i32| Ok(*x < 100)));
4739        let second = result.unwrap();
4740        assert_ne!(first, second);
4741        assert_eq!(sheet.cell_requirements(a), Some([first, second].as_slice()));
4742    }
4743
4744    #[test]
4745    /// Reports an ordinary cell's false requirement after propagation.
4746    fn add_requirement_defers_false_cell_requirement_until_propagation() {
4747        let mut sheet = Sheet::new();
4748        let a = sheet.add_cell(-5_i32);
4749        let requirement = sheet
4750            .add_requirement(a, Requirement::from_fn_1(a, |x: &i32| Ok(*x > 0)))
4751            .unwrap();
4752        sheet.propagate().unwrap();
4753        assert!(!sheet.cell_requirements_valid(a));
4754        assert_eq!(
4755            sheet.violated_requirements(a).collect::<Vec<_>>(),
4756            [requirement]
4757        );
4758    }
4759
4760    #[test]
4761    /// Reports a source cell's false requirement after propagation.
4762    fn add_requirement_defers_false_source_requirement_until_propagation() {
4763        let mut sheet = Sheet::new();
4764        let a = sheet.add_source(-5_i32);
4765        let requirement = sheet
4766            .add_requirement(a, Requirement::from_fn_1(a, |x: &i32| Ok(*x > 0)))
4767            .unwrap();
4768        sheet.propagate().unwrap();
4769        assert!(!sheet.cell_requirements_valid(a));
4770        assert_eq!(
4771            sheet.violated_requirements(a).collect::<Vec<_>>(),
4772            [requirement]
4773        );
4774    }
4775
4776    #[test]
4777    /// Propagates requirement-evaluation failure during propagation.
4778    fn add_requirement_defers_evaluation_error_until_propagation() {
4779        let mut sheet = Sheet::new();
4780        let a = sheet.add_cell(5_i32);
4781        sheet
4782            .add_requirement(
4783                a,
4784                Requirement::from_fn_1(a, |_: &i32| Err(anyhow::anyhow!("boom"))),
4785            )
4786            .unwrap();
4787        assert!(matches!(
4788            sheet.propagate(),
4789            Err(Error::MethodFailed { sites, .. }) if sites == [ErrorSite::Cell(a)]
4790        ));
4791    }
4792
4793    #[test]
4794    /// Checks lookup of the requirement attached to a cell.
4795    fn cell_has_the_requirement_it_was_given() {
4796        let mut sheet = Sheet::new();
4797        let a = sheet.add_cell(5_i32);
4798        let rid = sheet
4799            .add_requirement(a, Requirement::from_fn_1(a, |x: &i32| Ok(*x > 0)))
4800            .unwrap();
4801        assert_eq!(sheet.cells[a].requirements, vec![rid]);
4802    }
4803
4804    #[test]
4805    /// Checks that Out declaration returns its governed cell identity.
4806    fn add_out_returns_the_cell_id_directly() {
4807        let mut sheet = Sheet::new();
4808        let width = sheet.add_cell(4_i32);
4809        let height = sheet.add_cell(5_i32);
4810        let area = sheet.add_cell(0_i32);
4811        let cell = sheet
4812            .add_out(
4813                Method::from_fn_2_1([width, height], area, |w: &i32, h: &i32| Ok(w * h)),
4814                vec![],
4815            )
4816            .unwrap();
4817        assert_eq!(cell, area);
4818        assert_eq!(sheet.cell_kind(area), Some(CellKind::Out));
4819    }
4820
4821    #[test]
4822    /// Checks downstream use of a fixed Out cell as an ordinary input.
4823    fn out_cell_is_referenceable_as_another_relationships_input() {
4824        let mut sheet = Sheet::new();
4825        let width = sheet.add_cell(4_i32);
4826        let height = sheet.add_cell(5_i32);
4827        let area = sheet.add_cell(0_i32);
4828        let doubled = sheet.add_cell(0_i32);
4829        sheet
4830            .add_out(
4831                Method::from_fn_2_1([width, height], area, |w: &i32, h: &i32| Ok(w * h)),
4832                vec![],
4833            )
4834            .unwrap();
4835        let result =
4836            sheet.add_relationship(vec![Method::from_fn_1_1(
4837                area,
4838                doubled,
4839                |a: &i32| Ok(a * 2),
4840            )]);
4841        assert!(result.is_ok());
4842    }
4843
4844    #[test]
4845    /// Checks use of a fixed Out cell as a conditional subject.
4846    fn out_cell_is_referenceable_as_a_conditional_match_subject() {
4847        let mut sheet = Sheet::new();
4848        let flag = sheet.add_cell(0_i32);
4849        let derived_flag = sheet.add_cell(0_i32);
4850        sheet
4851            .add_out(
4852                Method::from_fn_1_1(flag, derived_flag, |f: &i32| Ok(*f)),
4853                vec![],
4854            )
4855            .unwrap();
4856        let result = sheet.add_conditional(
4857            MatchExpr::cell(derived_flag),
4858            Vec::<(Vec<i32>, Vec<RelationshipId>)>::new(),
4859            vec![],
4860        );
4861        assert!(result.is_ok());
4862    }
4863
4864    #[test]
4865    /// Checks rejection of a second fixed writer for an Out cell.
4866    fn add_out_returns_invalid_cell_kind_for_a_second_writer() {
4867        let mut sheet = Sheet::new();
4868        let width = sheet.add_cell(4_i32);
4869        let height = sheet.add_cell(5_i32);
4870        let area = sheet.add_cell(0_i32);
4871        sheet
4872            .add_out(Method::from_fn_1_1(width, area, |w: &i32| Ok(*w)), vec![])
4873            .unwrap();
4874        let result = sheet.add_out(Method::from_fn_1_1(height, area, |h: &i32| Ok(*h)), vec![]);
4875        assert!(matches!(result, Err(Error::InvalidCellKind { .. })));
4876    }
4877
4878    #[test]
4879    /// Checks legal Out promotion of a previously input-only cell.
4880    fn add_out_succeeds_when_target_cell_was_previously_used_only_as_an_input() {
4881        let mut sheet = Sheet::new();
4882        let a = sheet.add_cell(1_i32);
4883        let b = sheet.add_cell(2_i32);
4884        sheet
4885            .add_relationship(vec![Method::from_fn_1_1(a, b, |x: &i32| Ok(*x))])
4886            .unwrap();
4887        let c = sheet.add_cell(0_i32);
4888        sheet
4889            .add_out(
4890                Method::from_fn_1_1(c, a, |x: &i32| {
4891                    x.checked_mul(2).ok_or_else(|| anyhow::anyhow!("overflow"))
4892                }),
4893                vec![],
4894            )
4895            .unwrap();
4896        assert_eq!(sheet.cell_kind(a), Some(CellKind::Out));
4897        sheet.write(c, 9_i32).unwrap();
4898        sheet.propagate().unwrap();
4899        assert_eq!(*sheet.read::<i32>(a).unwrap(), 18);
4900        assert_eq!(*sheet.read::<i32>(b).unwrap(), 18);
4901    }
4902
4903    #[test]
4904    /// Checks deferred requirement evaluation on a newly declared Out cell.
4905    fn add_out_with_a_requirement_that_would_fail_still_succeeds_and_propagate_reports_it() {
4906        let mut sheet = Sheet::new();
4907        let width = sheet.add_cell(4_i32);
4908        let area = sheet.add_cell(0_i32);
4909        let area_cell = sheet
4910            .add_out(
4911                Method::from_fn_1_1(width, area, |w: &i32| Ok(*w)),
4912                vec![Requirement::from_fn_1(area, |a: &i32| Ok(*a > 100))],
4913            )
4914            .unwrap();
4915        sheet.propagate().unwrap();
4916        assert!(!sheet.cell_requirements_valid(area_cell));
4917        assert_eq!(sheet.violated_requirements(area_cell).count(), 1);
4918    }
4919
4920    #[test]
4921    /// Checks exclusion of ordinary cells from Out enumeration.
4922    fn out_cells_iterates_only_out_kind_cells() {
4923        let mut sheet = Sheet::new();
4924        let a = sheet.add_cell(1_i32);
4925        let b = sheet.add_cell(0_i32);
4926        let out_id = sheet
4927            .add_out(Method::from_fn_1_1(a, b, |x: &i32| Ok(*x)), vec![])
4928            .unwrap();
4929        let ids: Vec<CellId> = sheet.out_cells().collect();
4930        assert_eq!(ids, vec![out_id]);
4931    }
4932
4933    #[test]
4934    /// Checks legal diagnostic-filter attachment to a fixed Out cell.
4935    fn add_filter_succeeds_on_a_real_out_kind_cell() {
4936        let mut sheet = Sheet::new();
4937        let width = sheet.add_cell(4_i32);
4938        let area = sheet.add_cell(0_i32);
4939        let out_cell = sheet
4940            .add_out(Method::from_fn_1_1(width, area, |w: &i32| Ok(*w)), vec![])
4941            .unwrap();
4942        let result = sheet.add_filter(out_cell, Filter::from_fn_0(|x: &i32| Ok((*x).clamp(0, 10))));
4943        assert!(result.is_ok());
4944    }
4945
4946    #[test]
4947    /// Checks relevance contributors for a requirement on an ordinary cell.
4948    fn requirement_relevant_cells_covers_a_plain_cells_requirement_too() {
4949        let mut sheet = Sheet::new();
4950        let a = sheet.add_cell(5_i32);
4951        sheet
4952            .add_requirement(a, Requirement::from_fn_1(a, |x: &i32| Ok(*x > 0)))
4953            .unwrap();
4954        sheet.propagate().unwrap();
4955        assert!(sheet.requirement_relevant_cells().contains(&a));
4956    }
4957
4958    #[test]
4959    /// Checks violation contributors for a failed requirement on an ordinary cell.
4960    fn requirement_violation_cells_covers_a_plain_cells_violation_too() {
4961        let mut sheet = Sheet::new();
4962        let a = sheet.add_cell(200_i32);
4963        sheet
4964            .add_requirement(a, Requirement::from_fn_1(a, |x: &i32| Ok(*x > 100)))
4965            .unwrap();
4966        sheet.write(a, 5_i32).unwrap();
4967        sheet.propagate().unwrap();
4968        assert!(sheet.requirement_violation_cells().contains(&a));
4969    }
4970}