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