Skip to main content

quarb_model/
lib.rs

1//! Model files: derived arbor structure over any Quarb source.
2//!
3//! A [`Model`] (parsed from a `--model` file, see [`parse_model`])
4//! declares *derived* containers, references, and edges over a
5//! *base* arbor — the top rung of the reference-provenance ladder,
6//! generalizing the SQLite views-and-`--refs` construction to any
7//! substrate (a CSV, a JSON export, a log stream).
8//!
9//! [`ModelAdapter`] wraps a base adapter and presents the base's own
10//! nodes unchanged, adding the derived containers as new root
11//! children in a reserved node-id band (bit 63). Derived structure
12//! materializes lazily: a `node` constructor runs its query over the
13//! *base* (never over `self`, so no recursion) on first touch, and
14//! the distinct string-keyed values are cached. Declared references
15//! and edges build forward/reverse value indexes on first use.
16
17mod parse;
18
19pub use parse::{
20    AliasDecl, AliasKind, AliasRule, EdgeDecl, Model, Mount, NodeDecl, RefDecl, RelDecl, UnitsDecl,
21    parse_model, parse_model_file, resolve_mount_target,
22};
23
24use quarb::{AstAdapter, NodeId, QueryResult, Value};
25use std::cell::{Cell, OnceCell, RefCell};
26use std::collections::{BTreeMap, HashMap};
27
28/// Derived nodes carry this tag bit; the base's own ids never set it
29/// (the mount layer's index rides bits 56–63 but never reaches 128,
30/// so bit 63 stays clear for realistic inputs). Below it: the
31/// container index (bits 44–62) and the value index (bits 0–43).
32const MODEL_TAG: u64 = 1 << 63;
33const CIDX_SHIFT: u64 = 44;
34const VAL_MASK: u64 = (1 << CIDX_SHIFT) - 1;
35
36/// One member of a container: the two jobs a `node` constructor
37/// does, kept apart because they answer differently to "what does
38/// this node contain?".
39#[derive(Clone)]
40enum Member {
41    /// A constructor yielding *values* elevates them: each distinct
42    /// scalar becomes a node whose default projection is that value.
43    Value(Value),
44    /// A constructor yielding *nodes* aliases them: an existing node
45    /// given a container and a role, creating nothing. Such a node
46    /// holds everything its source holds, because it *is* the
47    /// source under a role.
48    Node(NodeId),
49}
50
51/// A derived container's materialized member set. A container
52/// declared by several `node` statements holds several roles: each
53/// statement appends its members under its own role, so
54/// `/tables/class` and `/tables/schema` share one container.
55struct Container {
56    name: String,
57    /// The members, in first-appearance order across the statements
58    /// that fill the container (member index `i` is member `i`, and
59    /// its node carries slot `i+1`; slot `0` names the container
60    /// node itself).
61    members: Vec<Member>,
62    /// Per member: the role it plays — `ip` in `/ips/ip` — which
63    /// names the child *and* labels every hop that lands on it, and
64    /// the trait it carries, the role unless declared.
65    roles: Vec<(String, String)>,
66    /// member key string → member index, over the whole container;
67    /// on a collision across roles the earlier statement wins.
68    by_str: HashMap<String, usize>,
69    /// role → (member key string → member index).
70    by_role: HashMap<String, HashMap<String, usize>>,
71}
72
73impl Container {
74    /// The member in node slot `v` (slot 0 is the container itself).
75    fn member(&self, v: usize) -> Option<Member> {
76        if v == 0 {
77            return None;
78        }
79        self.members.get(v - 1).cloned()
80    }
81
82    /// The role of the member in slot `v` (`v >= 1`).
83    fn role_of(&self, v: usize) -> &str {
84        &self.roles[v - 1].0
85    }
86
87    /// The trait of the member in slot `v` (`v >= 1`).
88    fn trait_of(&self, v: usize) -> &str {
89        &self.roles[v - 1].1
90    }
91
92    /// The 1-based position of slot `v` among the members of its
93    /// own role, for locators.
94    fn ordinal_in_role(&self, v: usize) -> usize {
95        let role = &self.roles[v - 1].0;
96        self.roles[..v - 1]
97            .iter()
98            .filter(|(r, _)| r == role)
99            .count()
100            + 1
101    }
102
103    /// The member indexes of one role, in order.
104    fn of_role<'a>(&'a self, role: &'a str) -> impl Iterator<Item = usize> + 'a {
105        self.roles
106            .iter()
107            .enumerate()
108            .filter(move |(_, (r, _))| r == role)
109            .map(|(i, _)| i)
110    }
111}
112
113/// The reference and edge fabric, built together on first use: it
114/// needs the containers materialized and one pass over each scope.
115struct Fabric {
116    /// base node → the derived node aliasing it, and that
117    /// container's role. A hop that would land on the raw node lands
118    /// on the alias instead, so the role a model declared is the one
119    /// the label uses.
120    alias: HashMap<NodeId, (NodeId, String)>,
121    /// (base node, field) → derived value node it resolves to.
122    resolve: HashMap<(NodeId, String), NodeId>,
123    /// The reverse of `resolve` and of every further element of a
124    /// list-valued field: per (target, field), the source nodes —
125    /// each once, the alias when a `node` gave the source a role.
126    sources: HashMap<(NodeId, String), Vec<NodeId>>,
127    /// derived value node → base nodes pointing at it, with the
128    /// field label (the backlink of a declared ref).
129    ref_back: HashMap<NodeId, Vec<(String, NodeId)>>,
130    /// base node → its outgoing declared refs (label, target node).
131    ref_fwd: HashMap<NodeId, Vec<(String, NodeId)>>,
132    /// source node → the nodes a declared `rel` relates it to, and
133    /// the reverse. One declaration serves both directions: an edge
134    /// is one thing, and both ends can walk it.
135    rel_fwd: HashMap<NodeId, Vec<(String, NodeId)>>,
136    rel_back: HashMap<NodeId, Vec<(String, NodeId)>>,
137    /// derived value node → container-labeled neighbours (the pair
138    /// edges; parallel edges collapsed, undirected so stored both
139    /// ways).
140    edges: HashMap<NodeId, Vec<(String, NodeId)>>,
141}
142
143/// A base arbor enriched with a model's derived structure.
144pub struct ModelAdapter<A: AstAdapter> {
145    base: A,
146    model: Model,
147    containers: OnceCell<Vec<Container>>,
148    fabric: OnceCell<Fabric>,
149    /// The declared traits' step bodies, parsed once; one per
150    /// declaration, in order.
151    bodies: OnceCell<Vec<quarb::StepBody>>,
152    /// What each declared trait decided at each node asked so far —
153    /// the step is tested at the node, never by a walk; the memo
154    /// spares re-testing a predicate the query asks about again.
155    decided: RefCell<HashMap<(usize, NodeId), bool>>,
156    /// How many declarations are visible while one is tested: a
157    /// declaration sees only the traits declared before it, so the
158    /// order of the file settles every reference and no cycle can
159    /// arise.
160    visible: Cell<usize>,
161    /// The declared indexes, built on first use: per declaration,
162    /// the canonical scope text, the scoped nodes, and the value
163    /// index over them. The referrer index (per relation label) is
164    /// built the first time a reverse arrow asks for it.
165    indexes: OnceCell<Vec<ValueIndex>>,
166    /// The declared trees, resolved to their nodes on first use.
167    trees: OnceCell<Vec<(String, NodeId)>>,
168    /// Per `ref` declaration, the nodes its scope selects — what
169    /// "a reference is declared on this node" means for a model.
170    ref_scopes: OnceCell<Vec<std::collections::HashSet<NodeId>>>,
171    referrers: RefCell<HashMap<(usize, Option<String>), HashMap<NodeId, Vec<usize>>>>,
172    /// The unit libraries' definitions (`units LIBRARY;`), loaded at
173    /// construction: name → definition, aliases included. An error
174    /// here is what `check` reports.
175    units: Result<BTreeMap<String, kaiv::faiv::UnitDef>, String>,
176}
177
178/// One `index SCOPE::prop;` declaration, materialized.
179struct ValueIndex {
180    scope: String,
181    property: String,
182    nodes: Vec<NodeId>,
183    by_value: HashMap<String, Vec<NodeId>>,
184}
185
186/// The key a value is indexed under: the engine's equality key.
187fn index_key(v: &Value) -> Option<String> {
188    v.equality_key()
189}
190
191impl<A: AstAdapter> ModelAdapter<A> {
192    fn value_indexes(&self) -> &Vec<ValueIndex> {
193        self.indexes.get_or_init(|| {
194            let defs = self.seeded_defs();
195            self.model
196                .indexes
197                .iter()
198                .map(|d| {
199                    let scope = quarb::parse_scope_path(&d.scope)
200                        .map(|sp| sp.text())
201                        .unwrap_or_else(|_| d.scope.clone());
202                    let nodes = self.view_nodes(&d.scope, &defs);
203                    let mut by_value: HashMap<String, Vec<NodeId>> = HashMap::new();
204                    for &n in &nodes {
205                        if let Some(v) = self.property(n, &d.property) {
206                            // A list-valued property is a set of values:
207                            // the node is found under each.
208                            let vals: Vec<&Value> = match &v {
209                                Value::List(items) => items.iter().collect(),
210                                other => vec![other],
211                            };
212                            for item in vals {
213                                if let Some(k) = index_key(item) {
214                                    by_value.entry(k).or_default().push(n);
215                                }
216                            }
217                        }
218                    }
219                    ValueIndex {
220                        scope,
221                        property: d.property.clone(),
222                        nodes,
223                        by_value,
224                    }
225                })
226                .collect()
227        })
228    }
229
230    fn ref_scopes(&self) -> &Vec<std::collections::HashSet<NodeId>> {
231        self.ref_scopes.get_or_init(|| {
232            let defs = self.seeded_defs();
233            self.model
234                .refs
235                .iter()
236                .map(|decl| {
237                    let mut scoped = self.scope_nodes(&decl.scope, &defs);
238                    if scoped.is_empty() {
239                        scoped = self
240                            .view_nodes(&decl.scope, &defs)
241                            .into_iter()
242                            .filter_map(|n| self.aliased(n))
243                            .collect();
244                    }
245                    scoped.into_iter().collect()
246                })
247                .collect()
248        })
249    }
250
251    /// The fields of the `ref` declarations whose scope holds the
252    /// source and whose target is the container the target node
253    /// stands in (a derived container, or a container of the
254    /// source it sits under).
255    fn declared_fields(&self, source: NodeId, target: NodeId) -> Vec<String> {
256        let src = self.aliased(source).unwrap_or(source);
257        let mut homes: Vec<String> = Vec::new();
258        match self.decode(target) {
259            Some((c, _)) => homes.push(self.containers()[c].name.clone()),
260            None => {
261                let mut at = self.base.parent(target);
262                while let Some(p) = at {
263                    if let Some(name) = self.base.name(p) {
264                        homes.push(name);
265                    }
266                    at = self.base.parent(p);
267                }
268            }
269        }
270        let mut out: Vec<String> = Vec::new();
271        for (decl, scope) in self.model.refs.iter().zip(self.ref_scopes()) {
272            if scope.contains(&src) && homes.contains(&decl.container) && !out.contains(&decl.field)
273            {
274                out.push(decl.field.clone());
275            }
276        }
277        out
278    }
279
280    fn value_index(&self, scope: &str, property: &str) -> Option<(usize, &ValueIndex)> {
281        self.value_indexes()
282            .iter()
283            .enumerate()
284            .find(|(_, ix)| ix.scope == scope && ix.property == property)
285    }
286
287    pub fn new(base: A, model: Model) -> Self {
288        let n = model.traits.len();
289        let model_units = model.units.clone();
290        ModelAdapter {
291            base,
292            model,
293            containers: OnceCell::new(),
294            fabric: OnceCell::new(),
295            bodies: OnceCell::new(),
296            decided: RefCell::new(HashMap::new()),
297            visible: Cell::new(n),
298            indexes: OnceCell::new(),
299            ref_scopes: OnceCell::new(),
300            trees: OnceCell::new(),
301            referrers: RefCell::new(HashMap::new()),
302            units: Self::load_units(&model_units),
303        }
304    }
305
306    /// Read every `units LIBRARY;` into one table. Refused: a file
307    /// that cannot be read or parsed, and a name the table already
308    /// holds — built in, or from a library imported earlier.
309    fn load_units(decls: &[UnitsDecl]) -> Result<BTreeMap<String, kaiv::faiv::UnitDef>, String> {
310        let mut table: BTreeMap<String, kaiv::faiv::UnitDef> = BTreeMap::new();
311        for decl in decls {
312            let bytes =
313                std::fs::read(&decl.path).map_err(|e| format!("units {}: {e}", decl.library))?;
314            let lib = kaiv::faiv::parse_faiv(&bytes)
315                .map_err(|e| format!("units {}: {e}", decl.library))?;
316            for (name, def) in lib.units {
317                if kaiv::unit::scale(&name).is_some() {
318                    return Err(format!(
319                        "units {}: '{name}' is a built-in unit already",
320                        decl.library
321                    ));
322                }
323                if table.contains_key(&name) {
324                    return Err(format!(
325                        "units {}: '{name}' is already defined by an earlier library",
326                        decl.library
327                    ));
328                }
329                table.insert(name, def);
330            }
331        }
332        Ok(table)
333    }
334
335    /// Everything a model can get wrong that only the source can
336    /// tell: call after construction, before the first query.
337    pub fn check(&self) -> Result<(), String> {
338        if let Err(e) = &self.units {
339            return Err(e.clone());
340        }
341        self.check_declared_traits()?;
342        self.check_ref_targets()?;
343        self.check_trees()
344    }
345
346    /// Refuse a `tree` whose path names no node.
347    pub fn check_trees(&self) -> Result<(), String> {
348        let defs = self.seeded_defs();
349        for decl in &self.model.trees {
350            if self.view_nodes(&decl.path, &defs).is_empty() {
351                return Err(format!(
352                    "tree '{}': the path {} names no node",
353                    decl.identity, decl.path
354                ));
355            }
356        }
357        Ok(())
358    }
359
360    /// Refuse a `ref` whose target names nothing: neither a
361    /// container a `node` statement declares nor a root-level node
362    /// of the source (a table, a mount, a top-level key). A typo in
363    /// the target would otherwise resolve nothing, silently. The
364    /// derived containers are not materialized for the check: their
365    /// names are in the declarations.
366    pub fn check_ref_targets(&self) -> Result<(), String> {
367        let defs = self.seeded_defs();
368        for decl in &self.model.refs {
369            if self.model.nodes.iter().any(|n| n.name == decl.container) {
370                continue;
371            }
372            let found = [
373                format!("/{}", decl.container),
374                format!("/\"{}\"", decl.container),
375            ]
376            .iter()
377            .any(|q| !self.scope_nodes(q, &defs).is_empty());
378            if !found {
379                return Err(format!(
380                    "ref target '{}' names nothing: no 'node /{}/…' statement \
381                     declares that container and the source has no \
382                     root-level '{}' (ref {}::{} --> {})",
383                    decl.container,
384                    decl.container,
385                    decl.container,
386                    decl.scope,
387                    decl.field,
388                    decl.path
389                ));
390            }
391        }
392        Ok(())
393    }
394
395    /// Refuse a declared trait that redefines one the arbor already
396    /// has: a trait the adapter puts on any node, or the trait a
397    /// `node` constructor gives a derived container's members. The
398    /// check walks the base arbor once per declaration; call it
399    /// after construction, before the first query.
400    pub fn check_declared_traits(&self) -> Result<(), String> {
401        for decl in &self.model.traits {
402            if let Some(n) = self.model.nodes.iter().find(|n| n.trait_name == decl.name) {
403                return Err(format!(
404                    "trait <{}> is already the trait of the derived container /{} — \
405                     a declared trait cannot redefine one the arbor has",
406                    decl.name, n.name
407                ));
408            }
409            let q = format!("*//*<{}> @| count", decl.name);
410            if let Ok(QueryResult::Values(vs)) = quarb::run(&q, &self.base)
411                && matches!(vs.first(), Some(Value::Int(n)) if *n > 0)
412            {
413                return Err(format!(
414                    "trait <{}> is the adapter's own — a declared trait cannot \
415                     redefine one the arbor has",
416                    decl.name
417                ));
418            }
419        }
420        Ok(())
421    }
422
423    /// The declared traits `node` carries: those whose step it would
424    /// match, among the declarations visible now.
425    fn declared_traits_of(&self, node: NodeId) -> Vec<String> {
426        let mut out = Vec::new();
427        for i in 0..self.visible.get().min(self.model.traits.len()) {
428            if self.declared_at(i, node) {
429                out.push(self.model.traits[i].name.clone());
430            }
431        }
432        out
433    }
434
435    /// Whether declaration `i` holds at `node`: its step body tested
436    /// at the node, over this adapter with only the earlier
437    /// declarations visible, memoized.
438    fn declared_at(&self, i: usize, node: NodeId) -> bool {
439        if let Some(&d) = self.decided.borrow().get(&(i, node)) {
440            return d;
441        }
442        let bodies = self.bodies.get_or_init(|| {
443            self.model
444                .traits
445                .iter()
446                .map(|t| {
447                    quarb::parse_step_body(&t.step).expect("validated when the model was parsed")
448                })
449                .collect()
450        });
451        let was = self.visible.get();
452        self.visible.set(i);
453        let d = quarb::node_matches_step_body(self, node, &bodies[i]);
454        self.visible.set(was);
455        self.decided.borrow_mut().insert((i, node), d);
456        d
457    }
458
459    pub fn base(&self) -> &A {
460        &self.base
461    }
462
463    /// A human-readable locator, composing the base's own renderer
464    /// for base nodes: `/container/value` for derived nodes.
465    pub fn locator(&self, node: NodeId, base_locator: impl Fn(NodeId) -> String) -> String {
466        match self.decode(node) {
467            None => base_locator(node),
468            Some((c, 0)) => format!("/{}", self.containers()[c].name),
469            Some((c, v)) => {
470                // Children share a role name, so the locator carries
471                // a position within the role, as a CSV row's does.
472                let cont = &self.containers()[c];
473                format!(
474                    "/{}/{}[{}]",
475                    cont.name,
476                    cont.role_of(v),
477                    cont.ordinal_in_role(v)
478                )
479            }
480        }
481    }
482
483    fn seeded_defs(&self) -> quarb::Defs {
484        quarb::parse_defs(&self.model.defs_text).unwrap_or_default()
485    }
486
487    /// The derived containers, materialized on first touch by running
488    /// each constructor over the base. A later constructor sees the
489    /// containers declared before it (chaining), so this fills the
490    /// vector incrementally.
491    fn containers(&self) -> &[Container] {
492        self.containers.get_or_init(|| {
493            let defs = self.seeded_defs();
494            let mut built: Vec<Container> = Vec::new();
495            for decl in &self.model.nodes {
496                let members = self.members(&decl.query, &defs, &built);
497                // A second statement naming an existing container
498                // fills it further under its own role.
499                let idx = match built.iter().position(|c| c.name == decl.name) {
500                    Some(i) => i,
501                    None => {
502                        built.push(Container {
503                            name: decl.name.clone(),
504                            members: Vec::new(),
505                            roles: Vec::new(),
506                            by_str: HashMap::new(),
507                            by_role: HashMap::new(),
508                        });
509                        built.len() - 1
510                    }
511                };
512                // Keys: an elevated value by its rendering; an aliased
513                // node by what it projects, so a `ref` into the
514                // container resolves against the same thing `::`
515                // would show.
516                let keyed: Vec<(Member, Option<String>)> = members
517                    .into_iter()
518                    .map(|m| {
519                        let key = match &m {
520                            Member::Value(v) => Some(v.to_string()),
521                            Member::Node(n) => self.base_key(*n),
522                        };
523                        (m, key)
524                    })
525                    .collect();
526                let cont = &mut built[idx];
527                // a role declared twice adds only members it does not
528                // hold: an elevated value by its rendering, an aliased
529                // node by its identity (rows of a table share a name,
530                // so their key is no identity)
531                let held_nodes: std::collections::HashSet<NodeId> = cont
532                    .members
533                    .iter()
534                    .zip(&cont.roles)
535                    .filter(|(_, (r, _))| *r == decl.role)
536                    .filter_map(|(m, _)| match m {
537                        Member::Node(n) => Some(*n),
538                        Member::Value(_) => None,
539                    })
540                    .collect();
541                let by_role = cont.by_role.entry(decl.role.clone()).or_default();
542                for (m, key) in keyed {
543                    let held = match &m {
544                        Member::Node(n) => held_nodes.contains(n),
545                        Member::Value(_) => key.as_deref().is_some_and(|k| by_role.contains_key(k)),
546                    };
547                    if held {
548                        continue;
549                    }
550                    let i = cont.members.len();
551                    cont.members.push(m);
552                    cont.roles
553                        .push((decl.role.clone(), decl.trait_name.clone()));
554                    if let Some(k) = key {
555                        by_role.entry(k.clone()).or_insert(i);
556                        cont.by_str.entry(k).or_insert(i);
557                    }
558                }
559            }
560            built
561        })
562    }
563
564    /// Run one constructor query and collect its members, in
565    /// first-appearance order: distinct values if it projects,
566    /// aliased nodes if it navigates. `prior` is the containers
567    /// already built (so a constructor may navigate an earlier
568    /// derived container) — exposed by wrapping the base in a partial
569    /// [`ModelAdapter`] over those.
570    fn members(&self, query: &str, defs: &quarb::Defs, prior: &[Container]) -> Vec<Member> {
571        // A constructor over the base alone is the common case; one
572        // that reaches an earlier derived container runs against a
573        // scratch enrichment holding just those.
574        let result = if prior.is_empty() {
575            quarb::run_with_defs(query, defs, &self.base)
576        } else {
577            let scratch = PriorView {
578                base: &self.base,
579                prior,
580            };
581            quarb::run_with_defs(query, defs, &scratch)
582        };
583        match result {
584            Ok(QueryResult::Values(vs)) => {
585                let mut seen = std::collections::HashSet::new();
586                vs.into_iter()
587                    .filter(|v| seen.insert(v.to_string()))
588                    .map(Member::Value)
589                    .collect()
590            }
591            Ok(QueryResult::Nodes(ns)) => {
592                let mut seen = std::collections::HashSet::new();
593                ns.into_iter()
594                    .filter(|n| seen.insert(*n))
595                    .map(Member::Node)
596                    .collect()
597            }
598            Err(_) => Vec::new(),
599        }
600    }
601
602    /// What a node projects, as a string — the key an aliased member
603    /// is found by.
604    fn base_key(&self, node: NodeId) -> Option<String> {
605        self.base
606            .default_value(node)
607            .map(|v| v.to_string())
608            .or_else(|| self.base.name(node))
609    }
610
611    /// The base node an aliased member stands for, if it is one.
612    fn aliased(&self, node: NodeId) -> Option<NodeId> {
613        match self.decode(node) {
614            Some((c, v)) if v > 0 => match self.containers()[c].member(v)? {
615                Member::Node(n) => Some(n),
616                Member::Value(_) => None,
617            },
618            _ => None,
619        }
620    }
621
622    /// The role of a derived member node, the label a hop landing
623    /// on it carries.
624    fn role_at(&self, node: NodeId) -> Option<String> {
625        match self.decode(node) {
626            Some((c, v)) if v > 0 => Some(self.containers()[c].role_of(v).to_string()),
627            _ => None,
628        }
629    }
630
631    /// An elevated member's value, if the node is one.
632    fn elevated(&self, node: NodeId) -> Option<Value> {
633        match self.decode(node) {
634            Some((c, v)) if v > 0 => match self.containers()[c].member(v)? {
635                Member::Value(val) => Some(val),
636                Member::Node(_) => None,
637            },
638            _ => None,
639        }
640    }
641
642    fn container_node(c: usize) -> NodeId {
643        NodeId(MODEL_TAG | (c as u64) << CIDX_SHIFT)
644    }
645
646    fn value_node(c: usize, v: usize) -> NodeId {
647        NodeId(MODEL_TAG | (c as u64) << CIDX_SHIFT | (v as u64 + 1))
648    }
649
650    /// Decode a derived node into `(container, value-slot)` where
651    /// slot 0 is the container node and slot `v` is value `v-1`.
652    /// `None` for a base node.
653    fn decode(&self, node: NodeId) -> Option<(usize, usize)> {
654        if node.0 & MODEL_TAG == 0 {
655            return None;
656        }
657        let c = ((node.0 & !MODEL_TAG) >> CIDX_SHIFT) as usize;
658        let v = (node.0 & VAL_MASK) as usize;
659        (c < self.containers().len()).then_some((c, v))
660    }
661
662    fn container_by_name(&self, name: &str) -> Option<usize> {
663        self.containers().iter().position(|c| c.name == name)
664    }
665
666    /// The reference and edge fabric, built on first use.
667    fn fabric(&self) -> &Fabric {
668        self.fabric.get_or_init(|| {
669            let defs = self.seeded_defs();
670            let mut f = Fabric {
671                alias: HashMap::new(),
672                resolve: HashMap::new(),
673                sources: HashMap::new(),
674                ref_back: HashMap::new(),
675                ref_fwd: HashMap::new(),
676                rel_fwd: HashMap::new(),
677                rel_back: HashMap::new(),
678                edges: HashMap::new(),
679            };
680            for (c, cont) in self.containers().iter().enumerate() {
681                for (i, m) in cont.members.iter().enumerate() {
682                    if let Member::Node(n) = m {
683                        f.alias
684                            .entry(*n)
685                            .or_insert((Self::value_node(c, i), cont.roles[i].0.clone()));
686                    }
687                }
688            }
689            // References: for each scoped base node, resolve its
690            // field value into the target container. What each
691            // (node, field) reaches is kept for the edges below.
692            let mut reached: HashMap<(NodeId, String), Vec<(String, NodeId)>> = HashMap::new();
693            for decl in &self.model.refs {
694                // The target's key index: a derived container's
695                // members (within the role the path names, when it
696                // names one), keyed by the explicit target property
697                // or the member key; or, when no container answers to
698                // the name, the nodes any path selects over the base
699                // and derived view (another mount's rows), keyed by
700                // the target property or their default projection.
701                // The first node with a key wins a collision.
702                let mut index: HashMap<String, NodeId> = HashMap::new();
703                let derived = self.container_by_name(&decl.container);
704                match derived {
705                    Some(container) => {
706                        let cont = &self.containers()[container];
707                        let slots: Vec<usize> = match decl.role.as_deref() {
708                            Some(role) => cont.of_role(role).map(|i| i + 1).collect(),
709                            None => (1..=cont.members.len()).collect(),
710                        };
711                        for v in slots {
712                            let n = Self::value_node(container, v - 1);
713                            let key = match decl.key_field.as_deref() {
714                                Some(f) => self.property(n, f).map(|k| k.to_string()),
715                                None => match cont.member(v) {
716                                    Some(Member::Value(val)) => Some(val.to_string()),
717                                    Some(Member::Node(b)) => self.base_key(b),
718                                    None => None,
719                                },
720                            };
721                            if let Some(k) = key {
722                                index.entry(k).or_insert(n);
723                            }
724                        }
725                    }
726                    None => {
727                        let view = PriorView {
728                            base: &self.base,
729                            prior: self.containers(),
730                        };
731                        // The bare form (`--> authors`) with no derived
732                        // container of that name is the root-level
733                        // container the source has: its children,
734                        // keyed by name (a table's rows by their key).
735                        let path = if decl.path.starts_with('/') {
736                            decl.path.clone()
737                        } else {
738                            format!("/{}/*", decl.path)
739                        };
740                        for n in self.view_nodes(&path, &defs) {
741                            let key = match decl.key_field.as_deref() {
742                                Some(f) => view.property(n, f).map(|k| k.to_string()),
743                                None => view
744                                    .default_value(n)
745                                    .map(|v| v.to_string())
746                                    .or_else(|| view.name(n)),
747                            };
748                            if let Some(k) = key {
749                                index.entry(k).or_insert(n);
750                            }
751                        }
752                    }
753                }
754                // A hop to a base target is labelled by the last
755                // named segment of the target path (`--кузнецова`);
756                // a derived target by the role of the node it lands on.
757                let path_label = scope_role(&decl.path);
758                // The source may be written through a role
759                // (`ref /library/book::author --> …`): the role nodes
760                // stand for the base rows beneath them.
761                let mut scoped = self.scope_nodes(&decl.scope, &defs);
762                if scoped.is_empty() {
763                    scoped = self
764                        .view_nodes(&decl.scope, &defs)
765                        .into_iter()
766                        .filter_map(|n| self.aliased(n))
767                        .collect();
768                }
769                for node in scoped {
770                    let Some(value) = self.base.property(node, &decl.field) else {
771                        continue;
772                    };
773                    if matches!(value, Value::Null) {
774                        continue;
775                    }
776                    // A list-valued property (a page's tags) refers
777                    // to one node per element: the first element is
778                    // what `-->` resolves to, every element gets its
779                    // hop and its backlink.
780                    let elements: Vec<Value> = match value {
781                        l @ Value::List(_) => l.into_items().unwrap_or_default(),
782                        other => vec![other],
783                    };
784                    let mut first = true;
785                    for value in elements {
786                        let Some(target) = index.get(&value.to_string()).copied() else {
787                            continue;
788                        };
789                        let fwd = self.role_at(target).unwrap_or_else(|| path_label.clone());
790                        // Backward the hop lands on the source node — on
791                        // the alias if a `node` gave it a role, else on
792                        // the raw node, named by the path that found it.
793                        let (back_node, back) = match f.alias.get(&node) {
794                            Some((alias, role)) => (*alias, role.clone()),
795                            None => (node, scope_role(&decl.scope)),
796                        };
797                        if first {
798                            f.resolve.insert((node, decl.field.clone()), target);
799                            first = false;
800                        }
801                        reached
802                            .entry((node, decl.field.clone()))
803                            .or_default()
804                            .push((fwd.clone(), target));
805                        let from = f.sources.entry((target, decl.field.clone())).or_default();
806                        if !from.contains(&back_node) {
807                            from.push(back_node);
808                        }
809                        f.ref_fwd.entry(node).or_default().push((fwd, target));
810                        f.ref_back
811                            .entry(target)
812                            .or_default()
813                            .push((back, back_node));
814                    }
815                }
816            }
817            // Relations: a condition evaluated per pair, with `$$`
818            // standing for the source node. Resolution is the special
819            // case where the condition is fixed (`the target whose
820            // identity is this value`) and so can go unwritten; a
821            // `rel` says its own, which is why it may hold for many
822            // targets and fork threads like any other hop.
823            for decl in &self.model.rels {
824                let fwd = scope_role(&decl.target);
825                let back = scope_role(&decl.source);
826                for source in self.view_nodes(&decl.source, &defs) {
827                    let cond = self.bind_driver(&decl.cond, source);
828                    let query = format!("{}{}", decl.target, cond);
829                    for target in self.view_nodes(&query, &defs) {
830                        if target == source {
831                            continue;
832                        }
833                        f.rel_fwd
834                            .entry(source)
835                            .or_default()
836                            .push((fwd.clone(), target));
837                        f.rel_back
838                            .entry(target)
839                            .or_default()
840                            .push((back.clone(), source));
841                    }
842                }
843            }
844            // Edges: per scoped node, connect what its two fields
845            // reach — the very nodes the refs resolved to, so an edge
846            // follows a ref by key property, into another mount, or
847            // with both ends in one role (a network of pairs) as it
848            // follows one into a value container. Each end is
849            // labelled by the role it lands in; parallel edges
850            // collapse.
851            for decl in &self.model.edges {
852                let mut seen = std::collections::HashSet::new();
853                for node in self.scope_nodes(&decl.scope, &defs) {
854                    let (Some(ends_a), Some(ends_b)) = (
855                        reached.get(&(node, decl.field_a.clone())),
856                        reached.get(&(node, decl.field_b.clone())),
857                    ) else {
858                        continue;
859                    };
860                    for (ra, na) in ends_a {
861                        for (rb, nb) in ends_b {
862                            if na != nb && seen.insert((*na, *nb)) {
863                                f.edges.entry(*na).or_default().push((rb.clone(), *nb));
864                                f.edges.entry(*nb).or_default().push((ra.clone(), *na));
865                            }
866                        }
867                    }
868                }
869            }
870            f
871        })
872    }
873
874    /// The base nodes selected by a scope path.
875    fn scope_nodes(&self, scope: &str, defs: &quarb::Defs) -> Vec<NodeId> {
876        match quarb::run_with_defs(scope, defs, &self.base) {
877            Ok(QueryResult::Nodes(ns)) => ns,
878            _ => Vec::new(),
879        }
880    }
881
882    /// The nodes a path selects over the base *and* the derived
883    /// containers — the view a relation's two ends navigate, since
884    /// either may be derived. It never consults the fabric, so
885    /// building the fabric cannot recurse into itself.
886    fn view_nodes(&self, path: &str, defs: &quarb::Defs) -> Vec<NodeId> {
887        let view = PriorView {
888            base: &self.base,
889            prior: self.containers(),
890        };
891        match quarb::run_with_defs(path, defs, &view) {
892            Ok(QueryResult::Nodes(ns)) => ns,
893            _ => Vec::new(),
894        }
895    }
896
897    /// Substitute the driver operand in a relation's condition:
898    /// `_::field` becomes that property of the source node, bare
899    /// `$$_` its default projection (`$$` is the heritage spelling). Text inside string literals is
900    /// left alone.
901    fn bind_driver(&self, cond: &str, source: NodeId) -> String {
902        let view = PriorView {
903            base: &self.base,
904            prior: self.containers(),
905        };
906        let b: Vec<char> = cond.chars().collect();
907        let mut out = String::new();
908        let mut i = 0;
909        while i < b.len() {
910            match b[i] {
911                q @ ('\'' | '"') => {
912                    out.push(q);
913                    i += 1;
914                    while i < b.len() && b[i] != q {
915                        if b[i] == '\\' && i + 1 < b.len() {
916                            out.push(b[i]);
917                            i += 1;
918                        }
919                        out.push(b[i]);
920                        i += 1;
921                    }
922                    if i < b.len() {
923                        out.push(b[i]);
924                        i += 1;
925                    }
926                }
927                // `_` — the served node (the relation's source); `$$_`
928                // and the bare `$$` are its heritage spellings.
929                c @ ('$' | '_')
930                    if (c == '$' && b.get(i + 1) == Some(&'$'))
931                        || (c == '_'
932                            && !(i > 0
933                                && (b[i - 1].is_alphanumeric()
934                                    || b[i - 1] == '_'
935                                    || b[i - 1] == '-'))
936                            && !b.get(i + 1).is_some_and(|c| {
937                                c.is_alphanumeric() || *c == '_' || *c == '-'
938                            })) =>
939                {
940                    if c == '$' {
941                        i += 2;
942                        if b.get(i) == Some(&'_')
943                            && !b
944                                .get(i + 1)
945                                .is_some_and(|c| c.is_alphanumeric() || *c == '_' || *c == '-')
946                        {
947                            i += 1;
948                        }
949                    } else {
950                        i += 1;
951                    }
952                    let value = if b.get(i) == Some(&':') && b.get(i + 1) == Some(&':') {
953                        i += 2;
954                        let start = i;
955                        while i < b.len() && (b[i].is_alphanumeric() || b[i] == '_' || b[i] == '-')
956                        {
957                            i += 1;
958                        }
959                        let field: String = b[start..i].iter().collect();
960                        view.property(source, &field)
961                    } else {
962                        view.default_value(source)
963                    };
964                    out.push_str(&literal(value.unwrap_or(Value::Null)));
965                }
966                c => {
967                    out.push(c);
968                    i += 1;
969                }
970            }
971        }
972        out
973    }
974}
975
976/// Render a value as query-source text, so a bound driver operand
977/// reads back as the literal it stands for.
978fn literal(v: Value) -> String {
979    match v {
980        Value::Int(n) => n.to_string(),
981        Value::Float(f) => f.to_string(),
982        Value::Bool(b) => b.to_string(),
983        Value::Null => "''".to_string(),
984        other => format!(
985            "'{}'",
986            other.to_string().replace('\\', "\\\\").replace('\'', "\\'")
987        ),
988    }
989}
990
991/// A read-only enrichment exposing just the already-built prior
992/// containers over the base — the view a chaining `node` constructor
993/// navigates. It never triggers further construction.
994struct PriorView<'a, A: AstAdapter> {
995    base: &'a A,
996    prior: &'a [Container],
997}
998
999impl<A: AstAdapter> AstAdapter for PriorView<'_, A> {
1000    fn root(&self) -> NodeId {
1001        self.base.root()
1002    }
1003    fn children(&self, node: NodeId) -> Vec<NodeId> {
1004        if node.0 & MODEL_TAG != 0 {
1005            let c = ((node.0 & !MODEL_TAG) >> CIDX_SHIFT) as usize;
1006            let v = (node.0 & VAL_MASK) as usize;
1007            if v == 0 && c < self.prior.len() {
1008                return (0..self.prior[c].members.len())
1009                    .map(|i| NodeId(MODEL_TAG | (c as u64) << CIDX_SHIFT | (i as u64 + 1)))
1010                    .collect();
1011            }
1012            return Vec::new();
1013        }
1014        let mut kids = self.base.children(node);
1015        if node == self.base.root() {
1016            for c in 0..self.prior.len() {
1017                kids.push(NodeId(MODEL_TAG | (c as u64) << CIDX_SHIFT));
1018            }
1019        }
1020        kids
1021    }
1022    fn name(&self, node: NodeId) -> Option<String> {
1023        if node.0 & MODEL_TAG != 0 {
1024            let c = ((node.0 & !MODEL_TAG) >> CIDX_SHIFT) as usize;
1025            let v = (node.0 & VAL_MASK) as usize;
1026            let cont = self.prior.get(c)?;
1027            // A derived child is named for its role, as in the full
1028            // adapter — the value stays on `::`.
1029            return Some(if v == 0 {
1030                cont.name.clone()
1031            } else {
1032                cont.role_of(v).to_string()
1033            });
1034        }
1035        self.base.name(node)
1036    }
1037    fn child_host(&self, node: NodeId) -> NodeId {
1038        if node.0 & MODEL_TAG != 0 {
1039            node
1040        } else {
1041            self.base.child_host(node)
1042        }
1043    }
1044    fn non_tree_nodes(&self) -> Vec<NodeId> {
1045        self.base.non_tree_nodes()
1046    }
1047    fn walks_for_references(&self) -> bool {
1048        self.base.walks_for_references()
1049    }
1050    fn properties(&self, node: NodeId) -> Vec<(String, Value)> {
1051        if node.0 & MODEL_TAG != 0 {
1052            Vec::new()
1053        } else {
1054            self.base.properties(node)
1055        }
1056    }
1057    fn children_named(&self, node: NodeId, name: &str) -> Vec<NodeId> {
1058        if node == self.base.root() {
1059            let mut out = self.base.children_named(node, name);
1060            if let Some(c) = self.prior.iter().position(|k| k.name == name) {
1061                out.push(NodeId(MODEL_TAG | (c as u64) << CIDX_SHIFT));
1062            }
1063            return out;
1064        }
1065        if node.0 & MODEL_TAG != 0 {
1066            let c = ((node.0 & !MODEL_TAG) >> CIDX_SHIFT) as usize;
1067            let v = (node.0 & VAL_MASK) as usize;
1068            let Some(cont) = self.prior.get(c) else {
1069                return Vec::new();
1070            };
1071            if v == 0 {
1072                return cont
1073                    .of_role(name)
1074                    .map(|i| NodeId(MODEL_TAG | (c as u64) << CIDX_SHIFT | (i as u64 + 1)))
1075                    .collect();
1076            }
1077            return Vec::new();
1078        }
1079        self.base.children_named(node, name)
1080    }
1081    fn traits(&self, node: NodeId) -> Vec<String> {
1082        if node.0 & MODEL_TAG != 0 {
1083            let c = ((node.0 & !MODEL_TAG) >> CIDX_SHIFT) as usize;
1084            let v = (node.0 & VAL_MASK) as usize;
1085            return match self.prior.get(c) {
1086                Some(cont) if v > 0 => vec![cont.trait_of(v).to_string()],
1087                _ => Vec::new(),
1088            };
1089        }
1090        self.base.traits(node)
1091    }
1092    fn parent(&self, node: NodeId) -> Option<NodeId> {
1093        if node.0 & MODEL_TAG != 0 {
1094            let c = ((node.0 & !MODEL_TAG) >> CIDX_SHIFT) as usize;
1095            let v = (node.0 & VAL_MASK) as usize;
1096            return Some(if v == 0 {
1097                self.base.root()
1098            } else {
1099                NodeId(MODEL_TAG | (c as u64) << CIDX_SHIFT)
1100            });
1101        }
1102        self.base.parent(node)
1103    }
1104    fn property(&self, node: NodeId, name: &str) -> Option<Value> {
1105        if node.0 & MODEL_TAG != 0 {
1106            let c = ((node.0 & !MODEL_TAG) >> CIDX_SHIFT) as usize;
1107            let v = (node.0 & VAL_MASK) as usize;
1108            return match self.prior.get(c)?.member(v)? {
1109                // elevated members have no named properties — the
1110                // value lives on the bare projection only
1111                Member::Value(_) => None,
1112                Member::Node(n) => self.base.property(n, name),
1113            };
1114        }
1115        self.base.property(node, name)
1116    }
1117    fn default_value(&self, node: NodeId) -> Option<Value> {
1118        if node.0 & MODEL_TAG != 0 {
1119            let c = ((node.0 & !MODEL_TAG) >> CIDX_SHIFT) as usize;
1120            let v = (node.0 & VAL_MASK) as usize;
1121            return match self.prior.get(c)?.member(v)? {
1122                Member::Value(val) => Some(val),
1123                Member::Node(n) => self.base.default_value(n),
1124            };
1125        }
1126        self.base.default_value(node)
1127    }
1128    // Minimal on purpose: base nodes forward; prior-container nodes
1129    // answer default (constructor queries never project
1130    // `:::provenance` mid-derivation).
1131    fn document_by_ref(&self, id: &str) -> Option<NodeId> {
1132        self.base.document_by_ref(id)
1133    }
1134    fn descendants_named(&self, node: NodeId, name: &str) -> Option<Vec<(NodeId, usize)>> {
1135        if node.0 & MODEL_TAG != 0 || node == self.base.root() {
1136            return None;
1137        }
1138        self.base.descendants_named(node, name)
1139    }
1140    fn prefetch_links(&self, nodes: &[NodeId], dir: quarb::LinkDir) {
1141        let base: Vec<NodeId> = nodes
1142            .iter()
1143            .copied()
1144            .filter(|n| n.0 & MODEL_TAG == 0)
1145            .collect();
1146        if !base.is_empty() {
1147            self.base.prefetch_links(&base, dir);
1148        }
1149    }
1150    fn provenance(&self, node: NodeId) -> quarb::Provenance {
1151        if node.0 & MODEL_TAG != 0 {
1152            return quarb::Provenance::default();
1153        }
1154        self.base.provenance(node)
1155    }
1156    fn provenance_list(&self, node: NodeId) -> quarb::ProvenanceList {
1157        if node.0 & MODEL_TAG != 0 {
1158            return quarb::ProvenanceList::default();
1159        }
1160        self.base.provenance_list(node)
1161    }
1162}
1163
1164impl<A: AstAdapter> AstAdapter for ModelAdapter<A> {
1165    fn root(&self) -> NodeId {
1166        self.base.root()
1167    }
1168
1169    fn children(&self, node: NodeId) -> Vec<NodeId> {
1170        match self.decode(node) {
1171            // A container node's children are its value nodes.
1172            Some((c, 0)) => (0..self.containers()[c].members.len())
1173                .map(|v| Self::value_node(c, v))
1174                .collect(),
1175            // An elevated node is a leaf; an aliased one has the
1176            // children of the node it stands for.
1177            Some(_) => match self.aliased(node) {
1178                Some(base) => self.base.children(base),
1179                None => Vec::new(),
1180            },
1181            // A base node: its own children, plus (at the root) the
1182            // derived containers as new siblings.
1183            None => {
1184                let mut kids = self.base.children(node);
1185                if node == self.base.root() {
1186                    for c in 0..self.containers().len() {
1187                        kids.push(Self::container_node(c));
1188                    }
1189                }
1190                kids
1191            }
1192        }
1193    }
1194
1195    /// A base node's transparent links stay transparent; the
1196    /// model's own nodes host their own children.
1197    fn child_host(&self, node: NodeId) -> NodeId {
1198        match self.decode(node) {
1199            Some(_) => node,
1200            None => self.base.child_host(node),
1201        }
1202    }
1203    fn non_tree_nodes(&self) -> Vec<NodeId> {
1204        self.base.non_tree_nodes()
1205    }
1206    fn walks_for_references(&self) -> bool {
1207        self.base.walks_for_references()
1208    }
1209    /// A `ref` declared over the source whose target is the
1210    /// container the target node stands in (a derived container, or
1211    /// a container of the source it sits under); else the base's
1212    /// own declarations.
1213    fn can_refer_to(&self, source: NodeId, target: NodeId) -> Option<bool> {
1214        if !self.declared_fields(source, target).is_empty() {
1215            return Some(true);
1216        }
1217        if self.decode(source).is_none() && self.decode(target).is_none() {
1218            return self.base.can_refer_to(source, target);
1219        }
1220        None
1221    }
1222    /// The fields of the `ref`s declared over the source into the
1223    /// target's container; else the base's own declarations.
1224    fn reference_properties(&self, source: NodeId, target: NodeId) -> Vec<String> {
1225        let fields = self.declared_fields(source, target);
1226        if !fields.is_empty() {
1227            return fields;
1228        }
1229        if self.decode(source).is_none() && self.decode(target).is_none() {
1230            return self.base.reference_properties(source, target);
1231        }
1232        Vec::new()
1233    }
1234    fn resolves_references(&self) -> bool {
1235        !self.model.refs.is_empty() || self.base.resolves_references()
1236    }
1237    /// A base node's label is the base's to give; a derived node
1238    /// needs none (the model's refs resolve without labels).
1239    fn reverse_label(&self, target: NodeId) -> Option<String> {
1240        if self.decode(target).is_some() {
1241            return None;
1242        }
1243        self.base.reverse_label(target)
1244    }
1245    /// The declared trees first (a declaration names the same node
1246    /// as the mount's own target would, and wins), then the base's
1247    /// own identities.
1248    fn tree_identities(&self) -> Vec<(String, NodeId)> {
1249        let declared = self.trees.get_or_init(|| {
1250            let defs = self.seeded_defs();
1251            self.model
1252                .trees
1253                .iter()
1254                .filter_map(|d| {
1255                    self.view_nodes(&d.path, &defs)
1256                        .first()
1257                        .map(|&n| (d.identity.clone(), n))
1258                })
1259                .collect()
1260        });
1261        let mut out = declared.clone();
1262        out.extend(self.base.tree_identities());
1263        out
1264    }
1265    /// A property the model declares a `ref` for resolves by that
1266    /// declaration; any other asks the base.
1267    fn resolve_needs_label(&self, node: NodeId, property: &str) -> bool {
1268        self.decode(node).is_none()
1269            && !self.model.refs.iter().any(|r| r.field == property)
1270            && self.base.resolve_needs_label(node, property)
1271    }
1272    fn property_names(&self, node: NodeId) -> Vec<String> {
1273        if self.decode(node).is_none() {
1274            return self.base.property_names(node);
1275        }
1276        let mut ps: Vec<String> = self.properties(node).into_iter().map(|(k, _)| k).collect();
1277        for c in self.children(node) {
1278            if let Some(name) = self.name(c)
1279                && !ps.contains(&name)
1280            {
1281                ps.push(name);
1282            }
1283        }
1284        ps
1285    }
1286    fn index_lookup(&self, scope: &str, property: &str, value: &Value) -> Option<Vec<NodeId>> {
1287        let (_, ix) = self.value_index(scope, property)?;
1288        let key = index_key(value)?;
1289        Some(ix.by_value.get(&key).cloned().unwrap_or_default())
1290    }
1291    fn index_referrers(
1292        &self,
1293        scope: &str,
1294        property: &str,
1295        label: Option<&str>,
1296        target: NodeId,
1297    ) -> Option<Vec<NodeId>> {
1298        let Some((i, ix)) = self.value_index(scope, property) else {
1299            // No declared index: the base may read the scope by key
1300            // (a database), provided the model does not resolve
1301            // this property its own way.
1302            if self.decode(target).is_none() && !self.model.refs.iter().any(|r| r.field == property)
1303            {
1304                return self.base.index_referrers(scope, property, label, target);
1305            }
1306            return None;
1307        };
1308        // The walk tests a candidate under the given label and, with
1309        // none given, also under the target's own container (a
1310        // resolver that needs the relation named); the index answers
1311        // on the same terms, in the scope's order.
1312        let mut labels = vec![label.map(str::to_string)];
1313        if label.is_none()
1314            && let Some(implied) = self.reverse_label(target)
1315        {
1316            labels.push(Some(implied));
1317        }
1318        let mut at: Vec<usize> = Vec::new();
1319        for l in labels {
1320            let key = (i, l);
1321            if !self.referrers.borrow().contains_key(&key) {
1322                // Resolve each scoped node's property once; the map
1323                // answers every later target.
1324                let mut map: HashMap<NodeId, Vec<usize>> = HashMap::new();
1325                for (pos, &n) in ix.nodes.iter().enumerate() {
1326                    if let Some(t) = self.resolve(n, property, key.1.as_deref()) {
1327                        map.entry(t).or_default().push(pos);
1328                    }
1329                }
1330                self.referrers.borrow_mut().insert(key.clone(), map);
1331            }
1332            if let Some(hits) = self
1333                .referrers
1334                .borrow()
1335                .get(&key)
1336                .and_then(|m| m.get(&target))
1337            {
1338                at.extend(hits);
1339            }
1340        }
1341        at.sort_unstable();
1342        at.dedup();
1343        Some(at.into_iter().map(|pos| ix.nodes[pos]).collect())
1344    }
1345    fn properties(&self, node: NodeId) -> Vec<(String, Value)> {
1346        if node.0 & MODEL_TAG != 0 {
1347            Vec::new()
1348        } else {
1349            self.base.properties(node)
1350        }
1351    }
1352
1353    fn children_named(&self, node: NodeId, name: &str) -> Vec<NodeId> {
1354        // The root's fast path must see the derived containers too.
1355        if node == self.base.root() {
1356            let mut out = self.base.children_named(node, name);
1357            if let Some(c) = self.container_by_name(name) {
1358                out.push(Self::container_node(c));
1359            }
1360            return out;
1361        }
1362        match self.decode(node) {
1363            // Every child answers to the role; none answers to its
1364            // value (that is a predicate's job, not a name's).
1365            Some((c, 0)) => self.containers()[c]
1366                .of_role(name)
1367                .map(|i| Self::value_node(c, i))
1368                .collect(),
1369            Some(_) => match self.aliased(node) {
1370                Some(base) => self.base.children_named(base, name),
1371                None => Vec::new(),
1372            },
1373            None => self.base.children_named(node, name),
1374        }
1375    }
1376
1377    fn name(&self, node: NodeId) -> Option<String> {
1378        match self.decode(node) {
1379            Some((c, 0)) => Some(self.containers()[c].name.clone()),
1380            // A derived node is named for its role, not its value:
1381            // a name says what a node *is* where you found it. The
1382            // value stays in the value space, on `::`.
1383            Some((c, v)) => Some(self.containers()[c].role_of(v).to_string()),
1384            None => self.base.name(node),
1385        }
1386    }
1387
1388    fn parent(&self, node: NodeId) -> Option<NodeId> {
1389        match self.decode(node) {
1390            Some((_, 0)) => Some(self.base.root()),
1391            Some((c, _)) => Some(Self::container_node(c)),
1392            None => self.base.parent(node),
1393        }
1394    }
1395
1396    fn traits(&self, node: NodeId) -> Vec<String> {
1397        let mut out = match self.decode(node) {
1398            // Each derived value node carries its container's trait,
1399            // so mixed-type walk results self-describe (`[<ip>]`).
1400            Some((c, v)) if v > 0 => {
1401                let mut out = vec![self.containers()[c].trait_of(v).to_string()];
1402                // An aliased node is the source wearing a role, so it
1403                // keeps the traits the source already carried.
1404                if let Some(base) = self.aliased(node) {
1405                    out.extend(self.base.traits(base));
1406                }
1407                out
1408            }
1409            Some(_) => Vec::new(),
1410            None => self.base.traits(node),
1411        };
1412        // The model's declared traits, decided at the node.
1413        if !self.model.traits.is_empty() {
1414            out.extend(self.declared_traits_of(node));
1415        }
1416        out
1417    }
1418
1419    fn property(&self, node: NodeId, name: &str) -> Option<Value> {
1420        match self.decode(node) {
1421            // An aliased node answers with the source's own
1422            // properties. An elevated node has exactly one datum —
1423            // its value, on the *bare* projection (`::`) — and no
1424            // named properties: answering any name with the value
1425            // would let `::cookie` on an ip node return the ip.
1426            Some((_, v)) if v > 0 => match self.aliased(node) {
1427                Some(base) => self.base.property(base, name),
1428                None => None,
1429            },
1430            Some(_) => None,
1431            None => self.base.property(node, name),
1432        }
1433    }
1434
1435    fn default_value(&self, node: NodeId) -> Option<Value> {
1436        match self.decode(node) {
1437            Some((_, v)) if v > 0 => match self.aliased(node) {
1438                Some(base) => self.base.default_value(base),
1439                None => self.elevated(node),
1440            },
1441            Some(_) => None,
1442            None => self.base.default_value(node),
1443        }
1444    }
1445
1446    fn metadata(&self, node: NodeId, key: &str) -> Option<Value> {
1447        match self.decode(node) {
1448            Some((c, 0)) if key == "n-rows" => {
1449                Some(Value::Int(self.containers()[c].members.len() as i64))
1450            }
1451            Some(_) => None,
1452            None => self.base.metadata(node, key),
1453        }
1454    }
1455
1456    /// Model nodes add their own annotations (`n-rows`); base nodes
1457    /// keep the mounted adapter's, aliases included. A model node's
1458    /// id means nothing to the base, so it is never passed down
1459    /// (a mount would read a mount index out of it).
1460    fn aliased_metadata(&self, node: NodeId) -> &'static [&'static str] {
1461        match self.decode(node) {
1462            Some(_) => &[],
1463            None => self.base.aliased_metadata(node),
1464        }
1465    }
1466
1467    /// Data provenance. An aliased node *is* its base node under a
1468    /// role, so it answers that node's provenance. An elevated node
1469    /// is a new value the model made: it answers a derivation source
1470    /// only (`model:/container`), never the components of any row it
1471    /// was computed from — the same no-leak discipline `property`
1472    /// applies to elevated values.
1473    fn provenance(&self, node: NodeId) -> quarb::Provenance {
1474        match self.decode(node) {
1475            Some((c, _)) => match self.aliased(node) {
1476                Some(base) => self.base.provenance(base),
1477                None => quarb::Provenance {
1478                    source: Some(format!("model:/{}", self.containers()[c].name)),
1479                    ..Default::default()
1480                },
1481            },
1482            None => self.base.provenance(node),
1483        }
1484    }
1485    fn provenance_list(&self, node: NodeId) -> quarb::ProvenanceList {
1486        match self.decode(node) {
1487            Some((_, _)) => match self.aliased(node) {
1488                Some(base) => self.base.provenance_list(base),
1489                None => quarb::ProvenanceList::single(self.provenance(node)),
1490            },
1491            None => self.base.provenance_list(node),
1492        }
1493    }
1494
1495    /// A written name reaches a node under its canonical name or
1496    /// any `alias` the model declares for names.
1497    fn answers_to(&self, node: NodeId, name: &str) -> bool {
1498        if self.decode(node).is_none() && self.base.answers_to(node, name) {
1499            return true;
1500        }
1501        let Some(canonical) = self.name(node) else {
1502            return canonical_none(name);
1503        };
1504        if canonical == name {
1505            return true;
1506        }
1507        let aliases = self
1508            .model
1509            .aliases
1510            .iter()
1511            .filter(|a| a.kind == AliasKind::Name);
1512        for a in aliases {
1513            if a.admits(name, &canonical) {
1514                return true;
1515            }
1516            // An exact alias rewrites the written name; a base node
1517            // then answers under its own rules (a kind name, a stem,
1518            // a title), not only under its canonical spelling.
1519            if self.decode(node).is_none()
1520                && let AliasRule::Exact { alias, original } = &a.rule
1521                && alias == name
1522                && self.base.answers_to(node, original)
1523            {
1524                return true;
1525            }
1526        }
1527        false
1528    }
1529
1530    /// A written trait reaches a node under a canonical trait or any
1531    /// `alias <...>` the model declares.
1532    fn has_trait(&self, node: NodeId, name: &str) -> bool {
1533        let traits = self.traits(node);
1534        if traits.iter().any(|t| t == name) {
1535            return true;
1536        }
1537        self.model
1538            .aliases
1539            .iter()
1540            .filter(|a| a.kind == AliasKind::Trait)
1541            .any(|a| traits.iter().any(|t| a.admits(name, t)))
1542    }
1543
1544    fn resolve(&self, node: NodeId, property: &str, hint: Option<&str>) -> Option<NodeId> {
1545        // An aliased node resolves as the node it stands for.
1546        let under = self.aliased(node).unwrap_or(node);
1547        if let Some(&target) = self.fabric().resolve.get(&(under, property.to_string())) {
1548            return Some(target);
1549        }
1550        if self.decode(under).is_none() {
1551            return self.base.resolve(under, property, hint);
1552        }
1553        // A purely derived node (a class container, an elevated
1554        // value) has no base identity to resolve through — and base
1555        // adapters index by their own ids, so a tagged id must never
1556        // reach them.
1557        None
1558    }
1559
1560    fn external_ref(
1561        &self,
1562        node: NodeId,
1563        property: &str,
1564        hint: Option<&str>,
1565    ) -> Option<quarb::ExternalRef> {
1566        // An aliased node references as the node it stands for; a
1567        // base id passes straight through, and a purely derived
1568        // node (tagged id) references nothing external.
1569        let under = self.aliased(node).unwrap_or(node);
1570        if self.decode(under).is_none() {
1571            return self.base.external_ref(under, property, hint);
1572        }
1573        None
1574    }
1575
1576    fn resolve_fragment(&self, node: NodeId, fragment: &str) -> Option<NodeId> {
1577        let under = self.aliased(node).unwrap_or(node);
1578        if self.decode(under).is_none() {
1579            return self.base.resolve_fragment(under, fragment);
1580        }
1581        None
1582    }
1583
1584    fn ref_property(&self, node: NodeId) -> Option<String> {
1585        let under = self.aliased(node).unwrap_or(node);
1586        if self.decode(under).is_none() {
1587            return self.base.ref_property(under);
1588        }
1589        None
1590    }
1591
1592    fn ref_label(&self, node: NodeId, property: &str) -> Option<String> {
1593        let under = self.aliased(node).unwrap_or(node);
1594        if self.decode(under).is_none() {
1595            return self.base.ref_label(under, property);
1596        }
1597        None
1598    }
1599
1600    fn links(&self, node: NodeId) -> Vec<(String, NodeId)> {
1601        let f = self.fabric();
1602        match self.decode(node) {
1603            // A value node's crosslinks are its pair edges and any
1604            // relation declared from it.
1605            Some((_, v)) if v > 0 => {
1606                let mut out = f.edges.get(&node).cloned().unwrap_or_default();
1607                if let Some(rels) = f.rel_fwd.get(&node) {
1608                    out.extend(rels.iter().cloned());
1609                }
1610                if let Some(base) = self.aliased(node) {
1611                    out.extend(self.base.links(base));
1612                    if let Some(refs) = f.ref_fwd.get(&base) {
1613                        out.extend(refs.iter().cloned());
1614                    }
1615                }
1616                out
1617            }
1618            Some(_) => Vec::new(),
1619            // A base node: its own links plus any declared refs and
1620            // relations.
1621            None => {
1622                let mut out = self.base.links(node);
1623                if let Some(refs) = f.ref_fwd.get(&node) {
1624                    out.extend(refs.iter().cloned());
1625                }
1626                if let Some(rels) = f.rel_fwd.get(&node) {
1627                    out.extend(rels.iter().cloned());
1628                }
1629                out
1630            }
1631        }
1632    }
1633
1634    fn backlinks(&self, node: NodeId) -> Vec<(String, NodeId)> {
1635        let f = self.fabric();
1636        match self.decode(node) {
1637            // A value node: the base nodes whose declared ref points
1638            // here, its pair edges (undirected), and the far end of
1639            // any relation declared toward it.
1640            Some((_, v)) if v > 0 => {
1641                let mut out = f.ref_back.get(&node).cloned().unwrap_or_default();
1642                if let Some(e) = f.edges.get(&node) {
1643                    out.extend(e.iter().cloned());
1644                }
1645                if let Some(rels) = f.rel_back.get(&node) {
1646                    out.extend(rels.iter().cloned());
1647                }
1648                out
1649            }
1650            Some(_) => Vec::new(),
1651            // A base node: its own backlinks, the declared refs that
1652            // resolve to it (a ref may target any path, so another
1653            // mount's row is a target too), and the relations toward it.
1654            None => {
1655                let mut out = self.base.backlinks(node);
1656                if let Some(refs) = f.ref_back.get(&node) {
1657                    out.extend(refs.iter().cloned());
1658                }
1659                if let Some(rels) = f.rel_back.get(&node) {
1660                    out.extend(rels.iter().cloned());
1661                }
1662                out
1663            }
1664        }
1665    }
1666
1667    /// The document the base holds under a URL — base ids pass
1668    /// straight through this layer.
1669    fn document_by_ref(&self, id: &str) -> Option<NodeId> {
1670        self.base.document_by_ref(id)
1671    }
1672
1673    /// The base's reverse index answers only when the model adds
1674    /// no reference fabric of its own; otherwise the walk sees
1675    /// both.
1676    fn reverse_resolve(
1677        &self,
1678        node: NodeId,
1679        property: Option<&str>,
1680        hint: Option<&str>,
1681    ) -> Option<Vec<NodeId>> {
1682        // A field the model declares a ref for: the fabric knows its
1683        // sources, each once — the walk would meet a source twice,
1684        // as the base row and as the role node over it.
1685        if let Some(field) = property
1686            && self.model.refs.iter().any(|r| r.field == field)
1687        {
1688            let f = self.fabric();
1689            let target = f.alias.get(&node).map_or(node, |(alias, _)| *alias);
1690            let mut out = f
1691                .sources
1692                .get(&(target, field.to_string()))
1693                .cloned()
1694                .unwrap_or_default();
1695            if target != node
1696                && let Some(more) = f.sources.get(&(node, field.to_string()))
1697            {
1698                for n in more {
1699                    if !out.contains(n) {
1700                        out.push(*n);
1701                    }
1702                }
1703            }
1704            return Some(out);
1705        }
1706        if self.decode(node).is_some()
1707            || !self.model.refs.is_empty()
1708            || !self.model.rels.is_empty()
1709            || !self.model.edges.is_empty()
1710        {
1711            return None;
1712        }
1713        self.base.reverse_resolve(node, property, hint)
1714    }
1715
1716    /// The base's name index answers below any base node but the
1717    /// root (the model's containers hang there) and only when no
1718    /// name alias could widen the match.
1719    fn descendants_named(&self, node: NodeId, name: &str) -> Option<Vec<(NodeId, usize)>> {
1720        if self.decode(node).is_some()
1721            || node == self.base.root()
1722            || self.model.aliases.iter().any(|a| a.kind == AliasKind::Name)
1723        {
1724            return None;
1725        }
1726        self.base.descendants_named(node, name)
1727    }
1728
1729    fn descendants_where(
1730        &self,
1731        node: NodeId,
1732        name: &str,
1733        property: &str,
1734        value: &Value,
1735    ) -> Option<Vec<(NodeId, usize)>> {
1736        if self.decode(node).is_some() || node == self.base.root() || !self.model.aliases.is_empty()
1737        {
1738            return None;
1739        }
1740        self.base.descendants_where(node, name, property, value)
1741    }
1742
1743    fn select_plan(&self, scope: &[&str], plan: &quarb::StepPlan<'_>) -> Option<Vec<bool>> {
1744        // A derived container or an alias is the model's to walk.
1745        if !self.model.aliases.is_empty()
1746            || scope
1747                .first()
1748                .is_some_and(|c| self.container_by_name(c).is_some())
1749        {
1750            return None;
1751        }
1752        self.base.select_plan(scope, plan)
1753    }
1754
1755    fn estimate(&self, scope: &[&str], plan: &quarb::StepPlan<'_>) -> Option<quarb::Estimate> {
1756        if !self.model.aliases.is_empty()
1757            || scope
1758                .first()
1759                .is_some_and(|c| self.container_by_name(c).is_some())
1760        {
1761            return None;
1762        }
1763        self.base.estimate(scope, plan)
1764    }
1765
1766    fn select(&self, from: NodeId, plan: &quarb::StepPlan<'_>) -> Option<quarb::Selection> {
1767        // A child hop sees the derived containers (`children_named`
1768        // does); a descendant search is the base's, except from the
1769        // root, from a derived node, or under aliases, where the
1770        // walk answers.
1771        match plan.axis {
1772            quarb::PlanAxis::Child => quarb::adapter::select_by_hooks(self, from, plan),
1773            quarb::PlanAxis::Descendant => {
1774                if self.decode(from).is_some()
1775                    || from == self.base.root()
1776                    || !self.model.aliases.is_empty()
1777                {
1778                    return None;
1779                }
1780                self.base.select(from, plan)
1781            }
1782        }
1783    }
1784
1785    fn has_node(&self, node: NodeId) -> bool {
1786        self.decode(node).is_some() || self.base.has_node(node)
1787    }
1788
1789    /// An edge's own properties are the base's: a model adds links
1790    /// (refs, relations) that carry none, and a node standing for a
1791    /// base one reads through to it.
1792    fn link_property(
1793        &self,
1794        source: NodeId,
1795        label: &str,
1796        target: NodeId,
1797        name: &str,
1798    ) -> Option<Value> {
1799        let base_of = |n: NodeId| match self.decode(n) {
1800            None => Some(n),
1801            Some(_) => self.aliased(n),
1802        };
1803        self.base
1804            .link_property(base_of(source)?, label, base_of(target)?, name)
1805    }
1806
1807    fn prefetch_links(&self, nodes: &[NodeId], dir: quarb::LinkDir) {
1808        let base: Vec<NodeId> = nodes
1809            .iter()
1810            .copied()
1811            .filter(|n| self.decode(*n).is_none())
1812            .collect();
1813        if !base.is_empty() {
1814            self.base.prefetch_links(&base, dir);
1815        }
1816    }
1817
1818    fn quantifier_bound(&self) -> usize {
1819        self.base.quantifier_bound()
1820    }
1821    /// The imported libraries first (`units LIBRARY;`), then the
1822    /// source's own.
1823    fn unit_scale(&self, expr: &str) -> Option<(f64, String)> {
1824        if let Ok(customs) = &self.units
1825            && !customs.is_empty()
1826            && let Some(hit) = kaiv::unit::scale_with(expr, customs)
1827        {
1828            return Some(hit);
1829        }
1830        self.base.unit_scale(expr)
1831    }
1832}
1833
1834/// An unnamed node answers to no written name.
1835fn canonical_none(_name: &str) -> bool {
1836    false
1837}
1838
1839/// The role of the nodes a scope path selects: the last *named*
1840/// segment of the path (`/posts/*` -> `posts`, `/row` -> `row`).
1841/// A base node's own name cannot serve — a relational row is named
1842/// by its primary key, which is an identity, not a role — so the
1843/// path that selected it supplies the word instead.
1844fn scope_role(scope: &str) -> String {
1845    scope
1846        .split('/')
1847        .filter(|seg| {
1848            !seg.is_empty()
1849                && !seg.starts_with('*')
1850                && !seg.starts_with('[')
1851                && !seg.chars().next().is_some_and(|c| c.is_ascii_digit())
1852        })
1853        .next_back()
1854        .unwrap_or("")
1855        .split(['[', ':'])
1856        .next()
1857        .unwrap_or("")
1858        .to_string()
1859}
1860
1861/// A borrowing adapter: lets a [`ModelAdapter`] enrich an adapter a
1862/// caller holds by reference (behind `dyn`) without taking
1863/// ownership — the shape both `qua`'s `run` funnel and `quai`'s
1864/// per-query dispatch need, since the concrete adapter is already
1865/// wrapped (now-binding, shell-gating) and only borrowed there.
1866pub struct Borrowed<'a>(pub &'a dyn AstAdapter);
1867
1868impl AstAdapter for Borrowed<'_> {
1869    quarb::forward_ast_adapter!(0);
1870}