Skip to main content

hirn_query/parser/
ast.rs

1//! AST types produced by the HirnQL parser.
2
3use std::fmt;
4
5use hirn_core::types::{EdgeRelation, Layer};
6
7// ── Top-level statement ────────────────────────────────────────────────
8
9/// A parsed HirnQL statement.
10#[derive(Debug, Clone, PartialEq)]
11pub enum Statement {
12    Recall(Box<RecallStmt>),
13    RecallEvents(RecallEventsStmt),
14    Think(Box<ThinkStmt>),
15    Correct(CorrectStmt),
16    Supersede(SupersedeStmt),
17    MergeMemory(MergeMemoryStmt),
18    Retract(RetractStmt),
19    Inspect(InspectStmt),
20    History(HistoryStmt),
21    Trace(TraceStmt),
22    Traverse(TraverseStmt),
23    Explain(ExplainStmt),
24    ExplainCauses(ExplainCausesStmt),
25    WhatIf(WhatIfStmt),
26    Counterfactual(CounterfactualStmt),
27    CreateRealm(CreateRealmStmt),
28    DropRealm(DropRealmStmt),
29    Grant(GrantStmt),
30    Revoke(RevokeStmt),
31    ShowPolicies(ShowPoliciesStmt),
32    ExplainPolicy(ExplainPolicyStmt),
33    ShowCluster,
34    SetTierPolicy(SetTierPolicyStmt),
35}
36
37// ── EXPLAIN ────────────────────────────────────────────────────────────
38
39#[derive(Debug, Clone, PartialEq)]
40pub struct ExplainStmt {
41    /// Whether EXPLAIN ANALYZE was used (execute + compare estimates).
42    pub analyze: bool,
43    /// The inner statement to explain.
44    pub inner: Box<Statement>,
45}
46
47// ── EXPLAIN CAUSES (Pearl Rung 1) ──────────────────────────────────────
48
49/// `EXPLAIN CAUSES "event" [IN <ns>] [DEPTH N]` — find causal chains backward.
50#[derive(Debug, Clone, PartialEq)]
51pub struct ExplainCausesStmt {
52    /// The event description to explain.
53    pub target: String,
54    /// Optional namespace scope.
55    pub namespace: Option<String>,
56    /// Max causal chain depth (default: 3).
57    pub depth: Option<usize>,
58}
59
60// ── WHAT_IF (Pearl Rung 2) ─────────────────────────────────────────────
61
62/// `WHAT_IF "intervention" THEN "outcome" [IN <ns>]` — simulate do-calculus.
63#[derive(Debug, Clone, PartialEq)]
64pub struct WhatIfStmt {
65    /// The intervention (do-variable).
66    pub intervention: String,
67    /// The outcome to evaluate.
68    pub outcome: String,
69    /// Optional namespace scope.
70    pub namespace: Option<String>,
71}
72
73// ── COUNTERFACTUAL (Pearl Rung 3) ──────────────────────────────────────
74
75/// `COUNTERFACTUAL "antecedent" THEN "consequent" [IN <ns>]` — reason about alternative histories.
76#[derive(Debug, Clone, PartialEq)]
77pub struct CounterfactualStmt {
78    /// The counterfactual antecedent (what didn't happen).
79    pub antecedent: String,
80    /// The consequent to evaluate.
81    pub consequent: String,
82    /// Optional namespace scope.
83    pub namespace: Option<String>,
84}
85
86// ── RECALL ─────────────────────────────────────────────────────────────
87
88#[derive(Debug, Clone, PartialEq, Eq)]
89pub enum RecallSnapshotAst {
90    Unqualified(String),
91    Observed(String),
92    Recorded(String),
93    Revision(String),
94}
95
96impl fmt::Display for RecallSnapshotAst {
97    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
98        match self {
99            Self::Unqualified(value) => write!(f, "\"{}\"", EscapeStr(value)),
100            Self::Observed(value) => write!(f, "OBSERVED \"{}\"", EscapeStr(value)),
101            Self::Recorded(value) => write!(f, "RECORDED \"{}\"", EscapeStr(value)),
102            Self::Revision(value) => write!(f, "REVISION \"{}\"", EscapeStr(value)),
103        }
104    }
105}
106
107#[derive(Debug, Clone, PartialEq)]
108pub struct RecallStmt {
109    pub layers: Vec<Layer>,
110    pub about: String,
111    pub involving: Option<Vec<String>>,
112    pub temporal: Option<TemporalClause>,
113    pub as_of: Option<RecallSnapshotAst>,
114    pub expand: Option<ExpandClause>,
115    pub follow_causes: Option<usize>,
116    pub where_clauses: Vec<WhereCondition>,
117    pub subquery_filters: Vec<SubqueryFilter>,
118    pub modality: Option<Vec<String>>,
119    pub resource_roles: Option<Vec<String>>,
120    pub hydration_modes: Option<Vec<String>>,
121    pub artifact_kinds: Option<Vec<String>>,
122    /// Depth scheduling: Auto (default), Full, Summary.
123    pub depth_mode: Option<DepthModeAst>,
124    /// WITH PROSPECTIVE ON|OFF (default: ON).
125    pub with_prospective: Option<bool>,
126    /// WITH MCFA_DEFENSE ON|OFF (default: OFF for recall).
127    pub with_mcfa: Option<bool>,
128    /// WITH CONFLICTS — include contradiction annotations.
129    pub with_conflicts: bool,
130    /// WITH PROVENANCE DEPTH N — expand DerivedFrom/PartOf edges (0 = no expansion).
131    pub provenance_depth: Option<usize>,
132    /// TOPIC "label" — scoped to a specific topic timeline.
133    pub topic: Option<String>,
134    pub group_by: Option<GroupByClause>,
135    pub projection: Option<Vec<String>>,
136    pub output_format: Option<OutputFormat>,
137    pub result_format: Option<OutputFormat>,
138    pub budget: Option<usize>,
139    pub namespace: Option<String>,
140    /// FROM REALM "a", "b" — cross-realm query (daemon-dispatched).
141    pub from_realms: Option<Vec<String>>,
142    pub consistency: Option<ConsistencyLevel>,
143    pub limit: Option<usize>,
144    /// Enable hybrid BM25+vector search.
145    pub hybrid: bool,
146}
147
148// ── RECALL EVENTS (audit query) ────────────────────────────────────────
149
150#[derive(Debug, Clone, PartialEq)]
151pub struct RecallEventsStmt {
152    /// Entity filter: `EVENTS FOR "entity"`.
153    pub entity_filter: Option<String>,
154    pub where_clauses: Vec<WhereCondition>,
155    pub temporal: Option<TemporalClause>,
156    pub namespace: Option<String>,
157    pub limit: Option<usize>,
158}
159
160// ── THINK ──────────────────────────────────────────────────────────────
161
162/// Retrieval mode for THINK statements.
163#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
164pub enum RetrievalMode {
165    /// Standard local retrieval (HNSW + spreading activation).
166    #[default]
167    Local,
168    /// Global retrieval via community summaries.
169    Global,
170    /// Both local and global, results merged.
171    Hybrid,
172    /// RAPTOR tree-based retrieval (Sarthi et al., 2024).
173    /// Top-down traversal through hierarchical summaries.
174    Raptor,
175    /// Adaptive retrieval (Jeong et al., NAACL 2024).
176    /// Automatically classifies query complexity and routes to the optimal strategy:
177    /// simple → local only, moderate → hybrid, complex → full pipeline with RAPTOR.
178    Adaptive,
179    /// Iterative multi-hop retrieval — retrieve → reformulate → retrieve loop.
180    Iterative,
181}
182
183#[derive(Debug, Clone, PartialEq)]
184pub struct ThinkStmt {
185    pub about: String,
186    pub involving: Option<Vec<String>>,
187    pub temporal: Option<TemporalClause>,
188    pub expand: Option<ExpandClause>,
189    pub follow_causes: Option<usize>,
190    pub where_clauses: Vec<WhereCondition>,
191    pub output_format: Option<OutputFormat>,
192    pub budget: Option<usize>,
193    pub namespace: Option<String>,
194    pub consistency: Option<ConsistencyLevel>,
195    pub limit: Option<usize>,
196    /// Enable hybrid BM25+vector search on the local THINK branch.
197    pub hybrid: bool,
198    pub mode: RetrievalMode,
199    /// Depth scheduling: Auto (default), Full, Summary.
200    pub depth_mode: Option<DepthModeAst>,
201    /// WITH PROSPECTIVE ON|OFF (default: ON).
202    pub with_prospective: Option<bool>,
203    /// WITH MCFA_DEFENSE ON|OFF.
204    pub with_mcfa: Option<bool>,
205    /// WITH PROVENANCE DEPTH N — expand DerivedFrom/PartOf edges (0 = no expansion).
206    pub provenance_depth: Option<usize>,
207    /// Maximum hops for iterative retrieval (default: 3).
208    pub max_hops: Option<usize>,
209    pub community_depth: Option<usize>,
210}
211
212// ── REMEMBER ───────────────────────────────────────────────────────────
213
214/// Multi-modal content parsed from HirnQL CONTENT IMAGE/CODE/AUDIO/VIDEO/DOCUMENT/STRUCTURED.
215#[derive(Debug, Clone, PartialEq, Eq)]
216pub enum ModalContent {
217    Image {
218        data: String,
219        description: String,
220    },
221    Code {
222        source: String,
223        language: String,
224    },
225    Audio {
226        data: String,
227        transcript: String,
228    },
229    Video {
230        data: String,
231        transcript: String,
232        description: String,
233    },
234    Document {
235        data: String,
236        title: String,
237    },
238    External {
239        uri: String,
240        title: String,
241        snippet: Option<String>,
242        mime_type: Option<String>,
243        checksum: Option<String>,
244        fetch_policy: Option<String>,
245        stale_at: Option<String>,
246    },
247    ToolOutput {
248        output: String,
249        tool: String,
250        mime_type: Option<String>,
251        schema: Option<String>,
252        call_id: Option<String>,
253        checksum: Option<String>,
254    },
255    Structured {
256        data: String,
257        schema: String,
258    },
259}
260
261/// A SET assignment in semantic mutation SET clauses.
262#[derive(Debug, Clone, PartialEq)]
263pub struct SetAssignment {
264    pub field: String,
265    pub value: SetValue,
266}
267
268/// Value for a SET assignment.
269#[derive(Debug, Clone, PartialEq)]
270pub enum SetValue {
271    Float(f64),
272    Int(i64),
273    String(String),
274    Max(String, f64),
275    Min(String, f64),
276}
277
278#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
279pub enum ForgetMode {
280    #[default]
281    Archive,
282    Purge,
283    /// Hard delete — irrecoverable.
284    Hard,
285}
286
287// ── CORRECT / RETRACT ─────────────────────────────────────────────────
288
289#[derive(Debug, Clone, PartialEq, Eq)]
290pub enum SemanticTargetRef {
291    Memory(String),
292    Logical(String),
293    Revision(String),
294}
295
296impl SemanticTargetRef {
297    #[must_use]
298    pub fn raw_value(&self) -> &str {
299        match self {
300            Self::Memory(value) | Self::Logical(value) | Self::Revision(value) => value,
301        }
302    }
303}
304
305#[derive(Debug, Clone, PartialEq)]
306pub struct CorrectStmt {
307    pub target: SemanticTargetRef,
308    pub updates: Vec<SetAssignment>,
309    pub reason: Option<String>,
310    pub observed_at: Option<String>,
311    pub caused_by: Option<String>,
312    pub namespace: Option<String>,
313}
314
315#[derive(Debug, Clone, PartialEq)]
316pub struct SupersedeStmt {
317    pub target: SemanticTargetRef,
318    pub updates: Vec<SetAssignment>,
319    pub reason: Option<String>,
320    pub observed_at: Option<String>,
321    pub caused_by: Option<String>,
322    pub namespace: Option<String>,
323}
324
325#[derive(Debug, Clone, PartialEq)]
326pub struct MergeMemoryStmt {
327    pub sources: Vec<SemanticTargetRef>,
328    pub target: SemanticTargetRef,
329    pub updates: Vec<SetAssignment>,
330    pub reason: Option<String>,
331    pub observed_at: Option<String>,
332    pub caused_by: Option<String>,
333    pub namespace: Option<String>,
334}
335
336#[derive(Debug, Clone, PartialEq, Eq)]
337pub struct RetractStmt {
338    pub target: SemanticTargetRef,
339    pub reason: Option<String>,
340    pub observed_at: Option<String>,
341    pub caused_by: Option<String>,
342    pub namespace: Option<String>,
343}
344
345// ── INSPECT ────────────────────────────────────────────────────────────
346
347#[derive(Debug, Clone, PartialEq, Eq)]
348pub struct InspectStmt {
349    pub target: SemanticTargetRef,
350}
351
352// ── HISTORY ───────────────────────────────────────────────────────────
353
354#[derive(Debug, Clone, PartialEq, Eq)]
355pub struct HistoryStmt {
356    pub target: SemanticTargetRef,
357    pub namespace: Option<String>,
358}
359
360// ── TRACE ──────────────────────────────────────────────────────────────
361
362#[derive(Debug, Clone, PartialEq, Eq)]
363pub struct TraceStmt {
364    pub target: SemanticTargetRef,
365}
366
367// ── Shared clause types ────────────────────────────────────────────────
368
369#[derive(Debug, Clone, PartialEq)]
370pub enum TemporalClause {
371    After(String),
372    Before(String),
373    Between { start: String, end: String },
374}
375
376#[derive(Debug, Clone, PartialEq)]
377pub struct ExpandClause {
378    pub depth: usize,
379    pub min_weight: Option<f32>,
380    pub activation: Option<ActivationModeAst>,
381}
382
383#[derive(Debug, Clone, Copy, PartialEq, Eq)]
384pub enum ActivationModeAst {
385    None,
386    Static,
387    Spreading,
388    /// Personalized PageRank (F-057).
389    Ppr,
390}
391
392/// Depth scheduling mode.
393#[derive(Debug, Clone, Copy, PartialEq, Eq)]
394pub enum DepthModeAst {
395    /// Let the engine classify automatically.
396    Auto,
397    /// Always run full pipeline.
398    Full,
399    /// Summary-only (skip graph activation).
400    Summary,
401}
402
403impl fmt::Display for DepthModeAst {
404    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
405        match self {
406            Self::Auto => write!(f, "AUTO"),
407            Self::Full => write!(f, "FULL"),
408            Self::Summary => write!(f, "SUMMARY"),
409        }
410    }
411}
412
413#[derive(Debug, Clone, PartialEq)]
414pub struct WhereCondition {
415    pub field: String,
416    pub op: ComparisonOp,
417    pub value: ConditionValue,
418}
419
420#[derive(Debug, Clone, Copy, PartialEq, Eq)]
421pub enum ComparisonOp {
422    Gt,
423    Lt,
424    Gte,
425    Lte,
426    Eq,
427    Neq,
428}
429
430#[derive(Debug, Clone, PartialEq)]
431pub enum ConditionValue {
432    Float(f64),
433    Int(i64),
434    String(String),
435    /// Unresolved parameter placeholder (e.g. `$1`, `$threshold`).
436    Param(String),
437}
438
439#[derive(Debug, Clone, Copy, PartialEq, Eq)]
440pub enum OutputFormat {
441    Narrative,
442    Context,
443    Graph,
444    CausalChain,
445    Json,
446    Csv,
447    Structured,
448}
449
450/// GROUP BY clause with aggregation function.
451#[derive(Debug, Clone, PartialEq)]
452pub struct GroupByClause {
453    pub field: String,
454    pub function: AggFunction,
455}
456
457/// Aggregation function.
458#[derive(Debug, Clone, Copy, PartialEq, Eq)]
459pub enum AggFunction {
460    Count,
461    Avg,
462    Sum,
463    Min,
464    Max,
465}
466
467#[derive(Debug, Clone, Copy, PartialEq, Eq)]
468pub enum ConsistencyLevel {
469    Linearizable,
470    Eventual,
471    Session,
472}
473
474/// A WHERE ... IN (subquery) filter.
475#[derive(Debug, Clone, PartialEq)]
476pub struct SubqueryFilter {
477    /// The field to match (e.g. "caused_by", "id").
478    pub field: String,
479    /// The inner subquery that produces IDs.
480    pub subquery: Subquery,
481}
482
483/// An inner RECALL subquery (used in WHERE ... IN (...)).
484#[derive(Debug, Clone, PartialEq)]
485pub struct Subquery {
486    pub layers: Vec<Layer>,
487    pub about: String,
488    pub involving: Option<Vec<String>>,
489    pub temporal: Option<TemporalClause>,
490    pub limit: Option<usize>,
491}
492
493// ── Display implementations ────────────────────────────────────────────
494
495impl fmt::Display for Statement {
496    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
497        match self {
498            Self::Recall(s) => write!(f, "{s}"),
499            Self::RecallEvents(s) => write!(f, "{s}"),
500            Self::Think(s) => write!(f, "{s}"),
501            Self::Correct(s) => write!(f, "{s}"),
502            Self::Supersede(s) => write!(f, "{s}"),
503            Self::MergeMemory(s) => write!(f, "{s}"),
504            Self::Retract(s) => write!(f, "{s}"),
505            Self::Inspect(s) => write!(f, "{s}"),
506            Self::History(s) => write!(f, "{s}"),
507            Self::Trace(s) => write!(f, "{s}"),
508            Self::Traverse(s) => write!(f, "{s}"),
509            Self::Explain(s) => write!(f, "{s}"),
510            Self::ExplainCauses(s) => write!(f, "{s}"),
511            Self::WhatIf(s) => write!(f, "{s}"),
512            Self::Counterfactual(s) => write!(f, "{s}"),
513            Self::CreateRealm(s) => write!(f, "{s}"),
514            Self::DropRealm(s) => write!(f, "{s}"),
515            Self::Grant(s) => write!(f, "{s}"),
516            Self::Revoke(s) => write!(f, "{s}"),
517            Self::ShowPolicies(s) => write!(f, "{s}"),
518            Self::ExplainPolicy(s) => write!(f, "{s}"),
519            Self::ShowCluster => write!(f, "SHOW CLUSTER"),
520            Self::SetTierPolicy(s) => write!(f, "{s}"),
521        }
522    }
523}
524
525impl fmt::Display for ExplainStmt {
526    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
527        write!(f, "EXPLAIN")?;
528        if self.analyze {
529            write!(f, " ANALYZE")?;
530        }
531        write!(f, " {}", self.inner)
532    }
533}
534
535impl fmt::Display for ExplainCausesStmt {
536    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
537        write!(f, "EXPLAIN CAUSES \"{}\"", EscapeStr(&self.target))?;
538        if let Some(ref ns) = self.namespace {
539            write!(f, " NAMESPACE {ns}")?;
540        }
541        if let Some(d) = self.depth {
542            write!(f, " DEPTH {d}")?;
543        }
544        Ok(())
545    }
546}
547
548impl fmt::Display for WhatIfStmt {
549    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
550        write!(
551            f,
552            "WHAT_IF \"{}\" THEN \"{}\"",
553            EscapeStr(&self.intervention),
554            EscapeStr(&self.outcome)
555        )?;
556        if let Some(ref ns) = self.namespace {
557            write!(f, " NAMESPACE {ns}")?;
558        }
559        Ok(())
560    }
561}
562
563impl fmt::Display for CounterfactualStmt {
564    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
565        write!(
566            f,
567            "COUNTERFACTUAL \"{}\" THEN \"{}\"",
568            EscapeStr(&self.antecedent),
569            EscapeStr(&self.consequent)
570        )?;
571        if let Some(ref ns) = self.namespace {
572            write!(f, " NAMESPACE {ns}")?;
573        }
574        Ok(())
575    }
576}
577
578fn write_layer_filter(f: &mut fmt::Formatter<'_>, layers: &[Layer]) -> fmt::Result {
579    for (i, l) in layers.iter().enumerate() {
580        if i > 0 {
581            write!(f, ", ")?;
582        }
583        write!(f, "{}", display_layer(*l))?;
584    }
585    Ok(())
586}
587
588fn display_layer(l: Layer) -> &'static str {
589    match l {
590        Layer::Episodic => "episodic",
591        Layer::Semantic => "semantic",
592        Layer::Working => "working",
593        Layer::Procedural => "procedural",
594    }
595}
596
597#[allow(clippy::too_many_arguments)]
598fn write_shared_clauses(
599    f: &mut fmt::Formatter<'_>,
600    about: &str,
601    involving: Option<&Vec<String>>,
602    temporal: Option<&TemporalClause>,
603    expand: Option<&ExpandClause>,
604    follow_causes: Option<usize>,
605    where_clauses: &[WhereCondition],
606    output_format: Option<OutputFormat>,
607    budget: Option<usize>,
608    namespace: Option<&String>,
609    consistency: Option<ConsistencyLevel>,
610    limit: Option<usize>,
611) -> fmt::Result {
612    write!(f, " ABOUT \"{}\"", EscapeStr(about))?;
613    if let Some(inv) = involving {
614        write!(f, " INVOLVING ")?;
615        write_string_list(f, inv)?;
616    }
617    if let Some(tc) = temporal {
618        write!(f, " {tc}")?;
619    }
620    if let Some(ex) = expand {
621        write!(f, " {ex}")?;
622    }
623    if let Some(d) = follow_causes {
624        write!(f, " FOLLOW CAUSES DEPTH {d}")?;
625    }
626    for wc in where_clauses {
627        write!(f, " {wc}")?;
628    }
629    if let Some(of) = output_format {
630        write!(f, " AS {of}")?;
631    }
632    if let Some(b) = budget {
633        write!(f, " BUDGET {b}")?;
634    }
635    if let Some(ns) = namespace {
636        write!(f, " NAMESPACE {ns}")?;
637    }
638    if let Some(c) = consistency {
639        write!(f, " CONSISTENCY {c}")?;
640    }
641    if let Some(l) = limit {
642        write!(f, " LIMIT {l}")?;
643    }
644    Ok(())
645}
646
647fn write_string_list(f: &mut fmt::Formatter<'_>, items: &[String]) -> fmt::Result {
648    for (i, item) in items.iter().enumerate() {
649        if i > 0 {
650            write!(f, ", ")?;
651        }
652        write!(f, "\"{}\"", EscapeStr(item))?;
653    }
654    Ok(())
655}
656
657fn write_semantic_target_list(
658    f: &mut fmt::Formatter<'_>,
659    items: &[SemanticTargetRef],
660) -> fmt::Result {
661    for (i, item) in items.iter().enumerate() {
662        if i > 0 {
663            write!(f, ", ")?;
664        }
665        write!(f, "{item}")?;
666    }
667    Ok(())
668}
669
670/// Zero-allocation string escaper — writes `\\` and `\"` directly to the
671/// formatter without heap-allocating an intermediate `String`.
672struct EscapeStr<'a>(&'a str);
673
674impl fmt::Display for EscapeStr<'_> {
675    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
676        let s = self.0;
677        let mut start = 0;
678        for (i, ch) in s.char_indices() {
679            let esc = match ch {
680                '\\' => "\\\\",
681                '"' => "\\\"",
682                _ => continue,
683            };
684            f.write_str(&s[start..i])?;
685            f.write_str(esc)?;
686            start = i + ch.len_utf8();
687        }
688        f.write_str(&s[start..])
689    }
690}
691
692impl fmt::Display for SemanticTargetRef {
693    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
694        match self {
695            Self::Memory(value) => write!(f, "\"{}\"", EscapeStr(value)),
696            Self::Logical(value) => write!(f, "LOGICAL \"{}\"", EscapeStr(value)),
697            Self::Revision(value) => write!(f, "REVISION \"{}\"", EscapeStr(value)),
698        }
699    }
700}
701
702impl fmt::Display for RecallStmt {
703    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
704        write!(f, "RECALL ")?;
705        write_layer_filter(f, &self.layers)?;
706        write_shared_clauses(
707            f,
708            &self.about,
709            self.involving.as_ref(),
710            self.temporal.as_ref(),
711            self.expand.as_ref(),
712            self.follow_causes,
713            &self.where_clauses,
714            self.output_format,
715            self.budget,
716            self.namespace.as_ref(),
717            self.consistency,
718            self.limit,
719        )?;
720        if let Some(ref snapshot) = self.as_of {
721            write!(f, " AS OF {snapshot}")?;
722        }
723        for sf in &self.subquery_filters {
724            write!(f, " WHERE {} IN ({})", sf.field, sf.subquery)?;
725        }
726        if let Some(ref modalities) = self.modality {
727            write!(f, " MODALITY {}", modalities.join(", "))?;
728        }
729        if let Some(ref resource_roles) = self.resource_roles {
730            write!(f, " RESOURCE_ROLE {}", resource_roles.join(", "))?;
731        }
732        if let Some(ref hydration_modes) = self.hydration_modes {
733            write!(f, " HYDRATION {}", hydration_modes.join(", "))?;
734        }
735        if let Some(ref artifact_kinds) = self.artifact_kinds {
736            write!(f, " ARTIFACT {}", artifact_kinds.join(", "))?;
737        }
738        if let Some(ref gb) = self.group_by {
739            write!(f, " GROUP BY {} {}", gb.field, gb.function)?;
740        }
741        if let Some(ref proj) = self.projection {
742            write!(f, " SELECT {}", proj.join(", "))?;
743        }
744        if let Some(ref rf) = self.result_format {
745            write!(f, " FORMAT {rf}")?;
746        }
747        if let Some(dm) = self.depth_mode {
748            write!(f, " DEPTH {dm}")?;
749        }
750        if let Some(ref topic) = self.topic {
751            write!(f, " TOPIC \"{}\"", EscapeStr(topic))?;
752        }
753        if let Some(wp) = self.with_prospective {
754            write!(f, " WITH PROSPECTIVE {}", if wp { "ON" } else { "OFF" })?;
755        }
756        if let Some(wm) = self.with_mcfa {
757            write!(f, " WITH MCFA_DEFENSE {}", if wm { "ON" } else { "OFF" })?;
758        }
759        if self.with_conflicts {
760            write!(f, " WITH CONFLICTS")?;
761        }
762        if let Some(pd) = self.provenance_depth {
763            write!(f, " WITH PROVENANCE DEPTH {pd}")?;
764        }
765        if let Some(ref realms) = self.from_realms {
766            write!(f, " FROM REALM ")?;
767            for (i, r) in realms.iter().enumerate() {
768                if i > 0 {
769                    write!(f, ", ")?;
770                }
771                write!(f, "\"{}\"", EscapeStr(r))?;
772            }
773        }
774        if self.hybrid {
775            write!(f, " HYBRID")?;
776        }
777        Ok(())
778    }
779}
780
781impl fmt::Display for RecallEventsStmt {
782    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
783        write!(f, "RECALL EVENTS")?;
784        if let Some(ref entity) = self.entity_filter {
785            write!(f, " FOR \"{}\"", EscapeStr(entity))?;
786        }
787        for wc in &self.where_clauses {
788            write!(f, " WHERE {wc}")?;
789        }
790        if let Some(ref tc) = self.temporal {
791            write!(f, " {tc}")?;
792        }
793        if let Some(ref ns) = self.namespace {
794            write!(f, " NAMESPACE {ns}")?;
795        }
796        if let Some(l) = self.limit {
797            write!(f, " LIMIT {l}")?;
798        }
799        Ok(())
800    }
801}
802
803impl fmt::Display for ThinkStmt {
804    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
805        write!(f, "THINK")?;
806        if self.mode == RetrievalMode::Global {
807            write!(f, " GLOBAL")?;
808        }
809        write_shared_clauses(
810            f,
811            &self.about,
812            self.involving.as_ref(),
813            self.temporal.as_ref(),
814            self.expand.as_ref(),
815            self.follow_causes,
816            &self.where_clauses,
817            self.output_format,
818            self.budget,
819            self.namespace.as_ref(),
820            self.consistency,
821            self.limit,
822        )?;
823        match self.mode {
824            RetrievalMode::Hybrid => write!(f, " MODE hybrid")?,
825            RetrievalMode::Raptor => write!(f, " MODE raptor")?,
826            RetrievalMode::Adaptive => write!(f, " MODE adaptive")?,
827            RetrievalMode::Iterative => {
828                write!(f, " MODE iterative")?;
829                if let Some(mh) = self.max_hops {
830                    write!(f, " MAX_HOPS {mh}")?;
831                }
832            }
833            _ => {}
834        }
835        if let Some(dm) = self.depth_mode {
836            write!(f, " DEPTH {dm}")?;
837        }
838        if let Some(wp) = self.with_prospective {
839            write!(f, " WITH PROSPECTIVE {}", if wp { "ON" } else { "OFF" })?;
840        }
841        if let Some(wm) = self.with_mcfa {
842            write!(f, " WITH MCFA_DEFENSE {}", if wm { "ON" } else { "OFF" })?;
843        }
844        if let Some(pd) = self.provenance_depth {
845            write!(f, " WITH PROVENANCE DEPTH {pd}")?;
846        }
847        if let Some(depth) = self.community_depth {
848            write!(f, " COMMUNITY_DEPTH {depth}")?;
849        }
850        if self.hybrid {
851            write!(f, " HYBRID")?;
852        }
853        Ok(())
854    }
855}
856
857impl fmt::Display for SetAssignment {
858    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
859        write!(f, "{} = {}", self.field, self.value)
860    }
861}
862
863impl fmt::Display for SetValue {
864    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
865        match self {
866            Self::Float(v) => write!(f, "{v}"),
867            Self::Int(v) => write!(f, "{v}"),
868            Self::String(v) => write!(f, "\"{v}\""),
869            Self::Max(field, val) => write!(f, "MAX({field}, {val})"),
870            Self::Min(field, val) => write!(f, "MIN({field}, {val})"),
871        }
872    }
873}
874
875impl fmt::Display for CorrectStmt {
876    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
877        write!(f, "CORRECT {} SET ", self.target)?;
878        for (i, update) in self.updates.iter().enumerate() {
879            if i > 0 {
880                write!(f, ", ")?;
881            }
882            write!(f, "{update}")?;
883        }
884        if let Some(ref reason) = self.reason {
885            write!(f, " REASON \"{}\"", EscapeStr(reason))?;
886        }
887        if let Some(ref observed_at) = self.observed_at {
888            write!(f, " OBSERVED AT \"{}\"", EscapeStr(observed_at))?;
889        }
890        if let Some(ref caused_by) = self.caused_by {
891            write!(f, " CAUSED BY \"{}\"", EscapeStr(caused_by))?;
892        }
893        if let Some(ref namespace) = self.namespace {
894            write!(f, " NAMESPACE {namespace}")?;
895        }
896        Ok(())
897    }
898}
899
900impl fmt::Display for SupersedeStmt {
901    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
902        write!(f, "SUPERSEDE {} SET ", self.target)?;
903        for (i, update) in self.updates.iter().enumerate() {
904            if i > 0 {
905                write!(f, ", ")?;
906            }
907            write!(f, "{update}")?;
908        }
909        if let Some(ref reason) = self.reason {
910            write!(f, " REASON \"{}\"", EscapeStr(reason))?;
911        }
912        if let Some(ref observed_at) = self.observed_at {
913            write!(f, " OBSERVED AT \"{}\"", EscapeStr(observed_at))?;
914        }
915        if let Some(ref caused_by) = self.caused_by {
916            write!(f, " CAUSED BY \"{}\"", EscapeStr(caused_by))?;
917        }
918        if let Some(ref namespace) = self.namespace {
919            write!(f, " NAMESPACE {namespace}")?;
920        }
921        Ok(())
922    }
923}
924
925impl fmt::Display for MergeMemoryStmt {
926    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
927        write!(f, "MERGE MEMORY ")?;
928        write_semantic_target_list(f, &self.sources)?;
929        write!(f, " INTO {}", self.target)?;
930        if !self.updates.is_empty() {
931            write!(f, " SET ")?;
932            for (i, update) in self.updates.iter().enumerate() {
933                if i > 0 {
934                    write!(f, ", ")?;
935                }
936                write!(f, "{update}")?;
937            }
938        }
939        if let Some(ref reason) = self.reason {
940            write!(f, " REASON \"{}\"", EscapeStr(reason))?;
941        }
942        if let Some(ref observed_at) = self.observed_at {
943            write!(f, " OBSERVED AT \"{}\"", EscapeStr(observed_at))?;
944        }
945        if let Some(ref caused_by) = self.caused_by {
946            write!(f, " CAUSED BY \"{}\"", EscapeStr(caused_by))?;
947        }
948        if let Some(ref namespace) = self.namespace {
949            write!(f, " NAMESPACE {namespace}")?;
950        }
951        Ok(())
952    }
953}
954
955impl fmt::Display for RetractStmt {
956    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
957        write!(f, "RETRACT {}", self.target)?;
958        if let Some(ref reason) = self.reason {
959            write!(f, " REASON \"{}\"", EscapeStr(reason))?;
960        }
961        if let Some(ref observed_at) = self.observed_at {
962            write!(f, " OBSERVED AT \"{}\"", EscapeStr(observed_at))?;
963        }
964        if let Some(ref caused_by) = self.caused_by {
965            write!(f, " CAUSED BY \"{}\"", EscapeStr(caused_by))?;
966        }
967        if let Some(ref namespace) = self.namespace {
968            write!(f, " NAMESPACE {namespace}")?;
969        }
970        Ok(())
971    }
972}
973
974impl fmt::Display for InspectStmt {
975    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
976        write!(f, "INSPECT {}", self.target)
977    }
978}
979
980impl fmt::Display for HistoryStmt {
981    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
982        write!(f, "HISTORY {}", self.target)?;
983        if let Some(ref namespace) = self.namespace {
984            write!(f, " NAMESPACE {namespace}")?;
985        }
986        Ok(())
987    }
988}
989
990impl fmt::Display for TraceStmt {
991    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
992        write!(f, "TRACE {}", self.target)
993    }
994}
995
996impl fmt::Display for TemporalClause {
997    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
998        match self {
999            Self::After(ts) => write!(f, "AFTER \"{ts}\""),
1000            Self::Before(ts) => write!(f, "BEFORE \"{ts}\""),
1001            Self::Between { start, end } => write!(f, "BETWEEN \"{start}\" AND \"{end}\""),
1002        }
1003    }
1004}
1005
1006impl fmt::Display for ExpandClause {
1007    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1008        write!(f, "EXPAND GRAPH DEPTH {}", self.depth)?;
1009        if let Some(mw) = self.min_weight {
1010            write!(f, " MIN_WEIGHT {mw}")?;
1011        }
1012        if let Some(am) = self.activation {
1013            write!(f, " ACTIVATION {am}")?;
1014        }
1015        Ok(())
1016    }
1017}
1018
1019impl fmt::Display for ActivationModeAst {
1020    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1021        match self {
1022            Self::None => write!(f, "none"),
1023            Self::Static => write!(f, "static"),
1024            Self::Spreading => write!(f, "spreading"),
1025            Self::Ppr => write!(f, "ppr"),
1026        }
1027    }
1028}
1029
1030impl fmt::Display for ComparisonOp {
1031    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1032        match self {
1033            Self::Gt => write!(f, ">"),
1034            Self::Lt => write!(f, "<"),
1035            Self::Gte => write!(f, ">="),
1036            Self::Lte => write!(f, "<="),
1037            Self::Eq => write!(f, "="),
1038            Self::Neq => write!(f, "!="),
1039        }
1040    }
1041}
1042
1043impl fmt::Display for ConditionValue {
1044    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1045        match self {
1046            Self::Float(value) => write!(f, "{value}"),
1047            Self::Int(value) => write!(f, "{value}"),
1048            Self::String(value) => write!(f, "\"{}\"", EscapeStr(value)),
1049            Self::Param(value) => write!(f, "{value}"),
1050        }
1051    }
1052}
1053
1054impl fmt::Display for OutputFormat {
1055    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1056        match self {
1057            Self::Narrative => write!(f, "narrative"),
1058            Self::Context => write!(f, "context"),
1059            Self::Graph => write!(f, "graph"),
1060            Self::CausalChain => write!(f, "causal_chain"),
1061            Self::Json => write!(f, "json"),
1062            Self::Csv => write!(f, "csv"),
1063            Self::Structured => write!(f, "structured"),
1064        }
1065    }
1066}
1067
1068impl fmt::Display for WhereCondition {
1069    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1070        write!(f, "WHERE {} {} {}", self.field, self.op, self.value)
1071    }
1072}
1073
1074impl fmt::Display for ConsistencyLevel {
1075    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1076        match self {
1077            Self::Linearizable => write!(f, "linearizable"),
1078            Self::Eventual => write!(f, "eventual"),
1079            Self::Session => write!(f, "session"),
1080        }
1081    }
1082}
1083
1084impl fmt::Display for AggFunction {
1085    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1086        match self {
1087            Self::Count => write!(f, "COUNT"),
1088            Self::Avg => write!(f, "AVG"),
1089            Self::Sum => write!(f, "SUM"),
1090            Self::Min => write!(f, "MIN"),
1091            Self::Max => write!(f, "MAX"),
1092        }
1093    }
1094}
1095
1096impl fmt::Display for Subquery {
1097    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1098        write!(f, "RECALL ")?;
1099        write_layer_filter(f, &self.layers)?;
1100        write!(f, " ABOUT \"{}\"", self.about)?;
1101        if let Some(ref inv) = self.involving {
1102            write!(f, " INVOLVING ")?;
1103            write_string_list(f, inv)?;
1104        }
1105        if let Some(ref tc) = self.temporal {
1106            write!(f, " {tc}")?;
1107        }
1108        if let Some(n) = self.limit {
1109            write!(f, " LIMIT {n}")?;
1110        }
1111        Ok(())
1112    }
1113}
1114
1115// ── TRAVERSE ───────────────────────────────────────────────────────────
1116
1117#[derive(Debug, Clone, PartialEq)]
1118pub struct TraverseStmt {
1119    pub from: String,
1120    pub via: Option<Vec<String>>,
1121    pub depth: usize,
1122    pub where_clauses: Vec<WhereCondition>,
1123    pub limit: Option<usize>,
1124    /// Namespace isolation for traversal results (F-SEC-11).
1125    pub namespace: Option<String>,
1126}
1127impl fmt::Display for TraverseStmt {
1128    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1129        write!(f, "TRAVERSE FROM \"{}\"", EscapeStr(&self.from))?;
1130        if let Some(ref via) = self.via {
1131            write!(f, " VIA {}", via.join(", "))?;
1132        }
1133        write!(f, " DEPTH {}", self.depth)?;
1134        if let Some(ref ns) = self.namespace {
1135            write!(f, " NAMESPACE \"{}\"", EscapeStr(ns))?;
1136        }
1137        for wc in &self.where_clauses {
1138            write!(f, " {wc}")?;
1139        }
1140        if let Some(n) = self.limit {
1141            write!(f, " LIMIT {n}")?;
1142        }
1143        Ok(())
1144    }
1145}
1146/// Parse a case-insensitive edge relation name (e.g. `"related_to"`, `"causes"`) into an [`EdgeRelation`].
1147///
1148/// Uses `eq_ignore_ascii_case` for zero-allocation matching.
1149pub fn parse_edge_relation(s: &str) -> Option<EdgeRelation> {
1150    const TABLE: &[(&str, EdgeRelation)] = &[
1151        ("related_to", EdgeRelation::RelatedTo),
1152        ("relatedto", EdgeRelation::RelatedTo),
1153        ("causes", EdgeRelation::Causes),
1154        ("caused_by", EdgeRelation::CausedBy),
1155        ("causedby", EdgeRelation::CausedBy),
1156        ("derived_from", EdgeRelation::DerivedFrom),
1157        ("derivedfrom", EdgeRelation::DerivedFrom),
1158        ("contradicts", EdgeRelation::Contradicts),
1159        ("supports", EdgeRelation::Supports),
1160        ("temporal_next", EdgeRelation::TemporalNext),
1161        ("temporalnext", EdgeRelation::TemporalNext),
1162        ("part_of", EdgeRelation::PartOf),
1163        ("partof", EdgeRelation::PartOf),
1164        ("instance_of", EdgeRelation::InstanceOf),
1165        ("instanceof", EdgeRelation::InstanceOf),
1166        ("similar_to", EdgeRelation::SimilarTo),
1167        ("similarto", EdgeRelation::SimilarTo),
1168        ("inhibits", EdgeRelation::Inhibits),
1169        ("participates_in", EdgeRelation::ParticipatesIn),
1170        ("participatesin", EdgeRelation::ParticipatesIn),
1171    ];
1172    TABLE
1173        .iter()
1174        .find(|(k, _)| s.eq_ignore_ascii_case(k))
1175        .map(|(_, v)| *v)
1176}
1177
1178// ── CREATE REALM / DROP REALM ─────────────────
1179
1180#[derive(Debug, Clone, PartialEq, Eq)]
1181pub struct CreateRealmStmt {
1182    pub name: String,
1183    pub description: Option<String>,
1184}
1185
1186impl fmt::Display for CreateRealmStmt {
1187    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1188        write!(f, "CREATE REALM \"{}\"", EscapeStr(&self.name))?;
1189        if let Some(ref desc) = self.description {
1190            write!(f, " DESCRIPTION \"{}\"", EscapeStr(desc))?;
1191        }
1192        Ok(())
1193    }
1194}
1195
1196#[derive(Debug, Clone, PartialEq, Eq)]
1197pub struct DropRealmStmt {
1198    pub name: String,
1199    pub confirm: bool,
1200}
1201
1202impl fmt::Display for DropRealmStmt {
1203    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1204        write!(f, "DROP REALM \"{}\"", EscapeStr(&self.name))?;
1205        if self.confirm {
1206            write!(f, " CONFIRM")?;
1207        }
1208        Ok(())
1209    }
1210}
1211
1212// ── GRANT / REVOKE ────────────────────────────
1213
1214/// Target of a GRANT/REVOKE: either a namespace or a realm.
1215#[derive(Debug, Clone, PartialEq, Eq)]
1216pub enum GrantTarget {
1217    Namespace(String),
1218    Realm(String),
1219}
1220
1221/// Principal reference: either an agent or a team.
1222#[derive(Debug, Clone, PartialEq, Eq)]
1223pub enum PrincipalRef {
1224    Agent(String),
1225    Team(String),
1226}
1227
1228impl PrincipalRef {
1229    /// Get the principal identifier string.
1230    pub fn id(&self) -> &str {
1231        match self {
1232            Self::Agent(a) => a,
1233            Self::Team(t) => t,
1234        }
1235    }
1236}
1237
1238#[derive(Debug, Clone, PartialEq, Eq)]
1239pub struct GrantStmt {
1240    pub actions: Vec<String>,
1241    pub target: GrantTarget,
1242    pub principal: PrincipalRef,
1243}
1244
1245impl fmt::Display for GrantStmt {
1246    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1247        write!(f, "GRANT {}", self.actions.join(", "))?;
1248        match &self.target {
1249            GrantTarget::Namespace(ns) => write!(f, " ON NAMESPACE \"{}\"", EscapeStr(ns))?,
1250            GrantTarget::Realm(r) => write!(f, " ON REALM \"{}\"", EscapeStr(r))?,
1251        }
1252        match &self.principal {
1253            PrincipalRef::Agent(a) => write!(f, " TO AGENT \"{}\"", EscapeStr(a))?,
1254            PrincipalRef::Team(t) => write!(f, " TO TEAM \"{}\"", EscapeStr(t))?,
1255        }
1256        Ok(())
1257    }
1258}
1259
1260#[derive(Debug, Clone, PartialEq, Eq)]
1261pub struct RevokeStmt {
1262    pub actions: Vec<String>,
1263    pub target: GrantTarget,
1264    pub principal: PrincipalRef,
1265}
1266
1267impl fmt::Display for RevokeStmt {
1268    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1269        write!(f, "REVOKE {}", self.actions.join(", "))?;
1270        match &self.target {
1271            GrantTarget::Namespace(ns) => write!(f, " ON NAMESPACE \"{}\"", EscapeStr(ns))?,
1272            GrantTarget::Realm(r) => write!(f, " ON REALM \"{}\"", EscapeStr(r))?,
1273        }
1274        match &self.principal {
1275            PrincipalRef::Agent(a) => write!(f, " FROM AGENT \"{}\"", EscapeStr(a))?,
1276            PrincipalRef::Team(t) => write!(f, " FROM TEAM \"{}\"", EscapeStr(t))?,
1277        }
1278        Ok(())
1279    }
1280}
1281
1282// ── SHOW POLICIES / EXPLAIN POLICY ───────────
1283
1284#[derive(Debug, Clone, PartialEq, Eq)]
1285pub struct ShowPoliciesStmt {
1286    pub principal: Option<PrincipalRef>,
1287}
1288
1289impl fmt::Display for ShowPoliciesStmt {
1290    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1291        write!(f, "SHOW POLICIES")?;
1292        if let Some(ref p) = self.principal {
1293            match p {
1294                PrincipalRef::Agent(a) => write!(f, " FOR AGENT \"{}\"", EscapeStr(a))?,
1295                PrincipalRef::Team(t) => write!(f, " FOR TEAM \"{}\"", EscapeStr(t))?,
1296            }
1297        }
1298        Ok(())
1299    }
1300}
1301
1302#[derive(Debug, Clone, PartialEq, Eq)]
1303pub struct ExplainPolicyStmt {
1304    pub principal: PrincipalRef,
1305    pub resource_type: String,
1306    pub resource_name: String,
1307    pub action: String,
1308}
1309
1310impl fmt::Display for ExplainPolicyStmt {
1311    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1312        write!(f, "EXPLAIN POLICY FOR ")?;
1313        match &self.principal {
1314            PrincipalRef::Agent(a) => write!(f, "AGENT \"{}\"", EscapeStr(a))?,
1315            PrincipalRef::Team(t) => write!(f, "TEAM \"{}\"", EscapeStr(t))?,
1316        }
1317        write!(
1318            f,
1319            " ON {} \"{}\" ACTION {}",
1320            self.resource_type.to_uppercase(),
1321            EscapeStr(&self.resource_name),
1322            self.action
1323        )
1324    }
1325}
1326
1327// ── SET TIER_POLICY ──────────────────────────
1328
1329/// A value in a `SET TIER_POLICY` assignment.
1330#[derive(Debug, Clone, PartialEq)]
1331pub enum TierPolicyValue {
1332    /// A string literal, e.g., `'2h'`.
1333    Str(String),
1334    /// A floating point number, e.g., `0.7`.
1335    Float(f64),
1336    /// An integer, e.g., `3600`.
1337    Int(i64),
1338}
1339
1340impl fmt::Display for TierPolicyValue {
1341    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1342        match self {
1343            Self::Str(s) => write!(f, "'{s}'"),
1344            Self::Float(v) => write!(f, "{v}"),
1345            Self::Int(v) => write!(f, "{v}"),
1346        }
1347    }
1348}
1349
1350/// `SET TIER_POLICY <field> = <value>`
1351#[derive(Debug, Clone, PartialEq)]
1352pub struct SetTierPolicyStmt {
1353    /// The policy field to set (e.g., `working_to_episodic_ttl`).
1354    pub field: String,
1355    /// The value to assign.
1356    pub value: TierPolicyValue,
1357}
1358
1359impl fmt::Display for SetTierPolicyStmt {
1360    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1361        write!(f, "SET TIER_POLICY {} = ", self.field)?;
1362        match &self.value {
1363            TierPolicyValue::Str(s) => write!(f, "'{}'", EscapeStr(s)),
1364            TierPolicyValue::Float(v) => write!(f, "{v}"),
1365            TierPolicyValue::Int(v) => write!(f, "{v}"),
1366        }
1367    }
1368}
1369
1370/// Returns `true` if the string is a parameter placeholder (`$1`, `$name`).
1371pub fn is_param(s: &str) -> bool {
1372    s.starts_with('$')
1373        && s.len() > 1
1374        && s[1..]
1375            .chars()
1376            .all(|c| c.is_ascii_alphanumeric() || c == '_')
1377}
1378
1379/// Extract all parameter placeholders from a statement.
1380///
1381/// Scans string fields that may hold `$param` references and returns
1382/// a deduped, sorted list of parameter names (including the `$` prefix).
1383pub fn collect_parameters(stmt: &Statement) -> Vec<String> {
1384    let mut params = Vec::new();
1385
1386    fn check(s: &str, params: &mut Vec<String>) {
1387        if is_param(s) && !params.contains(&s.to_string()) {
1388            params.push(s.to_string());
1389        }
1390    }
1391
1392    fn check_wheres(wcs: &[WhereCondition], params: &mut Vec<String>) {
1393        for wc in wcs {
1394            if let ConditionValue::Param(ref p) = wc.value
1395                && !params.contains(p)
1396            {
1397                params.push(p.clone());
1398            }
1399        }
1400    }
1401
1402    match stmt {
1403        Statement::Recall(r) => {
1404            check(&r.about, &mut params);
1405            check_wheres(&r.where_clauses, &mut params);
1406        }
1407        Statement::Think(t) => {
1408            check(&t.about, &mut params);
1409            check_wheres(&t.where_clauses, &mut params);
1410        }
1411        Statement::Correct(c) => {
1412            check(c.target.raw_value(), &mut params);
1413            if let Some(ref reason) = c.reason {
1414                check(reason, &mut params);
1415            }
1416            if let Some(ref observed_at) = c.observed_at {
1417                check(observed_at, &mut params);
1418            }
1419            if let Some(ref caused_by) = c.caused_by {
1420                check(caused_by, &mut params);
1421            }
1422        }
1423        Statement::Supersede(s) => {
1424            check(s.target.raw_value(), &mut params);
1425            if let Some(ref reason) = s.reason {
1426                check(reason, &mut params);
1427            }
1428            if let Some(ref observed_at) = s.observed_at {
1429                check(observed_at, &mut params);
1430            }
1431            if let Some(ref caused_by) = s.caused_by {
1432                check(caused_by, &mut params);
1433            }
1434        }
1435        Statement::MergeMemory(m) => {
1436            for source in &m.sources {
1437                check(source.raw_value(), &mut params);
1438            }
1439            check(m.target.raw_value(), &mut params);
1440            if let Some(ref reason) = m.reason {
1441                check(reason, &mut params);
1442            }
1443            if let Some(ref observed_at) = m.observed_at {
1444                check(observed_at, &mut params);
1445            }
1446            if let Some(ref caused_by) = m.caused_by {
1447                check(caused_by, &mut params);
1448            }
1449        }
1450        Statement::Retract(r) => {
1451            check(r.target.raw_value(), &mut params);
1452            if let Some(ref reason) = r.reason {
1453                check(reason, &mut params);
1454            }
1455            if let Some(ref observed_at) = r.observed_at {
1456                check(observed_at, &mut params);
1457            }
1458            if let Some(ref caused_by) = r.caused_by {
1459                check(caused_by, &mut params);
1460            }
1461        }
1462        Statement::Traverse(t) => {
1463            check(&t.from, &mut params);
1464            check_wheres(&t.where_clauses, &mut params);
1465        }
1466        Statement::Inspect(i) => check(i.target.raw_value(), &mut params),
1467        Statement::History(h) => check(h.target.raw_value(), &mut params),
1468        Statement::Trace(t) => check(t.target.raw_value(), &mut params),
1469        Statement::Explain(e) => return collect_parameters(&e.inner),
1470        Statement::RecallEvents(r) => check_wheres(&r.where_clauses, &mut params),
1471        Statement::CreateRealm(_)
1472        | Statement::DropRealm(_)
1473        | Statement::Grant(_)
1474        | Statement::Revoke(_)
1475        | Statement::ShowPolicies(_)
1476        | Statement::ExplainPolicy(_)
1477        | Statement::ShowCluster
1478        | Statement::SetTierPolicy(_) => {}
1479        Statement::ExplainCauses(e) => check(&e.target, &mut params),
1480        Statement::WhatIf(w) => {
1481            check(&w.intervention, &mut params);
1482            check(&w.outcome, &mut params);
1483        }
1484        Statement::Counterfactual(c) => {
1485            check(&c.antecedent, &mut params);
1486            check(&c.consequent, &mut params);
1487        }
1488    }
1489
1490    params.sort();
1491    params
1492}
1493
1494#[cfg(test)]
1495mod tests {
1496    use super::*;
1497
1498    #[test]
1499    fn display_recall() {
1500        let stmt = RecallStmt {
1501            layers: vec![Layer::Episodic, Layer::Semantic],
1502            about: "test query".into(),
1503            involving: None,
1504            temporal: None,
1505            expand: None,
1506            follow_causes: None,
1507            where_clauses: vec![],
1508            modality: None,
1509            resource_roles: None,
1510            hydration_modes: None,
1511            artifact_kinds: None,
1512            group_by: None,
1513            projection: None,
1514            output_format: None,
1515            result_format: None,
1516            as_of: None,
1517            subquery_filters: vec![],
1518            budget: None,
1519            namespace: None,
1520            consistency: None,
1521            limit: Some(10),
1522            hybrid: false,
1523            depth_mode: None,
1524            with_prospective: None,
1525            with_mcfa: None,
1526            with_conflicts: false,
1527            provenance_depth: None,
1528            topic: None,
1529            from_realms: None,
1530        };
1531        let s = stmt.to_string();
1532        assert!(s.starts_with("RECALL episodic, semantic ABOUT"));
1533        assert!(s.contains("LIMIT 10"));
1534    }
1535
1536    #[test]
1537    fn display_all_variants() {
1538        // Verify every Statement variant has a working Display
1539        let stmts: Vec<Statement> = vec![
1540            Statement::Recall(Box::new(RecallStmt {
1541                layers: vec![Layer::Episodic],
1542                about: "x".into(),
1543                involving: None,
1544                temporal: None,
1545                expand: None,
1546                follow_causes: None,
1547                where_clauses: vec![],
1548                modality: None,
1549                resource_roles: None,
1550                hydration_modes: None,
1551                artifact_kinds: None,
1552                group_by: None,
1553                projection: None,
1554                output_format: None,
1555                result_format: None,
1556                as_of: None,
1557                subquery_filters: vec![],
1558                budget: None,
1559                namespace: None,
1560                consistency: None,
1561                limit: None,
1562                hybrid: false,
1563                depth_mode: None,
1564                with_prospective: None,
1565                with_mcfa: None,
1566                with_conflicts: false,
1567                provenance_depth: None,
1568                topic: None,
1569                from_realms: None,
1570            })),
1571            Statement::Think(Box::new(ThinkStmt {
1572                about: "y".into(),
1573                involving: None,
1574                temporal: None,
1575                expand: None,
1576                follow_causes: None,
1577                where_clauses: vec![],
1578                output_format: None,
1579                budget: Some(4096),
1580                namespace: None,
1581                consistency: None,
1582                limit: None,
1583                hybrid: false,
1584                mode: RetrievalMode::Local,
1585                community_depth: None,
1586                depth_mode: None,
1587                with_prospective: None,
1588                with_mcfa: None,
1589                provenance_depth: None,
1590                max_hops: None,
1591            })),
1592            Statement::Correct(CorrectStmt {
1593                target: SemanticTargetRef::Memory("some_id".into()),
1594                updates: vec![SetAssignment {
1595                    field: "description".into(),
1596                    value: SetValue::String("updated".into()),
1597                }],
1598                reason: Some("fix".into()),
1599                observed_at: None,
1600                caused_by: None,
1601                namespace: None,
1602            }),
1603            Statement::Supersede(SupersedeStmt {
1604                target: SemanticTargetRef::Memory("some_id".into()),
1605                updates: vec![SetAssignment {
1606                    field: "description".into(),
1607                    value: SetValue::String("replacement".into()),
1608                }],
1609                reason: Some("new authority".into()),
1610                observed_at: None,
1611                caused_by: None,
1612                namespace: None,
1613            }),
1614            Statement::MergeMemory(MergeMemoryStmt {
1615                sources: vec![
1616                    SemanticTargetRef::Memory("some_id".into()),
1617                    SemanticTargetRef::Logical("other_id".into()),
1618                ],
1619                target: SemanticTargetRef::Revision("target_id".into()),
1620                updates: vec![SetAssignment {
1621                    field: "confidence".into(),
1622                    value: SetValue::Float(0.92),
1623                }],
1624                reason: Some("deduplicate".into()),
1625                observed_at: None,
1626                caused_by: None,
1627                namespace: None,
1628            }),
1629            Statement::Retract(RetractStmt {
1630                target: SemanticTargetRef::Memory("some_id".into()),
1631                reason: Some("obsolete".into()),
1632                observed_at: None,
1633                caused_by: None,
1634                namespace: None,
1635            }),
1636            Statement::Inspect(InspectStmt {
1637                target: SemanticTargetRef::Logical("id".into()),
1638            }),
1639            Statement::History(HistoryStmt {
1640                target: SemanticTargetRef::Revision("id".into()),
1641                namespace: Some("team_a".into()),
1642            }),
1643            Statement::Trace(TraceStmt {
1644                target: SemanticTargetRef::Memory("id".into()),
1645            }),
1646        ];
1647        for s in &stmts {
1648            let text = s.to_string();
1649            assert!(!text.is_empty(), "Display for {s:?} should not be empty");
1650        }
1651    }
1652
1653    #[test]
1654    fn parse_edge_relation_cases() {
1655        assert_eq!(
1656            parse_edge_relation("related_to"),
1657            Some(EdgeRelation::RelatedTo)
1658        );
1659        assert_eq!(parse_edge_relation("causes"), Some(EdgeRelation::Causes));
1660        assert_eq!(
1661            parse_edge_relation("CONTRADICTS"),
1662            Some(EdgeRelation::Contradicts)
1663        );
1664        assert_eq!(parse_edge_relation("unknown"), None);
1665    }
1666}