Skip to main content

hyperstack_interpreter/
ast.rs

1use serde::{Deserialize, Serialize};
2use serde_json::Value;
3use std::collections::BTreeMap;
4use std::marker::PhantomData;
5
6// ============================================================================
7// IDL Snapshot Types - Embedded IDL for AST-only compilation
8// ============================================================================
9
10/// Snapshot of an Anchor IDL for embedding in the AST
11/// Contains all information needed to generate parsers and SDK types
12#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct IdlSnapshot {
14    /// Program name (e.g., "pump")
15    pub name: String,
16    /// Program ID this IDL belongs to
17    #[serde(default, skip_serializing_if = "Option::is_none")]
18    pub program_id: Option<String>,
19    /// Program version
20    pub version: String,
21    /// Account type definitions
22    pub accounts: Vec<IdlAccountSnapshot>,
23    /// Instruction definitions
24    pub instructions: Vec<IdlInstructionSnapshot>,
25    /// Type definitions (structs, enums)
26    #[serde(default)]
27    pub types: Vec<IdlTypeDefSnapshot>,
28    /// Event definitions
29    #[serde(default)]
30    pub events: Vec<IdlEventSnapshot>,
31    /// Error definitions
32    #[serde(default)]
33    pub errors: Vec<IdlErrorSnapshot>,
34    /// Discriminant size in bytes (1 for Steel, 8 for Anchor)
35    /// Defaults to 8 (Anchor) for backwards compatibility
36    #[serde(default = "default_discriminant_size")]
37    pub discriminant_size: usize,
38}
39
40fn default_discriminant_size() -> usize {
41    8
42}
43
44/// Account definition from IDL
45#[derive(Debug, Clone, Serialize, Deserialize)]
46pub struct IdlAccountSnapshot {
47    /// Account name (e.g., "BondingCurve")
48    pub name: String,
49    /// 8-byte discriminator
50    pub discriminator: Vec<u8>,
51    /// Documentation
52    #[serde(default)]
53    pub docs: Vec<String>,
54    #[serde(default, skip_serializing_if = "Option::is_none")]
55    pub serialization: Option<IdlSerializationSnapshot>,
56}
57
58#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
59#[serde(rename_all = "lowercase")]
60pub enum IdlSerializationSnapshot {
61    Borsh,
62    Bytemuck,
63    #[serde(alias = "bytemuckunsafe")]
64    BytemuckUnsafe,
65}
66
67/// Instruction definition from IDL
68#[derive(Debug, Clone, Serialize, Deserialize)]
69pub struct IdlInstructionSnapshot {
70    /// Instruction name (e.g., "buy", "sell", "create")
71    pub name: String,
72    /// 8-byte discriminator
73    pub discriminator: Vec<u8>,
74    /// Documentation
75    #[serde(default)]
76    pub docs: Vec<String>,
77    /// Account arguments
78    pub accounts: Vec<IdlInstructionAccountSnapshot>,
79    /// Data arguments
80    pub args: Vec<IdlFieldSnapshot>,
81}
82
83/// Account argument in an instruction
84#[derive(Debug, Clone, Serialize, Deserialize)]
85pub struct IdlInstructionAccountSnapshot {
86    /// Account name (e.g., "mint", "user")
87    pub name: String,
88    /// Whether this account is writable
89    #[serde(default)]
90    pub writable: bool,
91    /// Whether this account is a signer
92    #[serde(default)]
93    pub signer: bool,
94    /// Optional - if the account is optional
95    #[serde(default)]
96    pub optional: bool,
97    /// Fixed address constraint (if any)
98    #[serde(default)]
99    pub address: Option<String>,
100    /// Documentation
101    #[serde(default)]
102    pub docs: Vec<String>,
103}
104
105/// Field definition (used in instructions, accounts, types)
106#[derive(Debug, Clone, Serialize, Deserialize)]
107pub struct IdlFieldSnapshot {
108    /// Field name
109    pub name: String,
110    /// Field type
111    #[serde(rename = "type")]
112    pub type_: IdlTypeSnapshot,
113}
114
115/// Type representation from IDL
116#[derive(Debug, Clone, Serialize, Deserialize)]
117#[serde(untagged)]
118pub enum IdlTypeSnapshot {
119    /// Simple types: "u64", "bool", "string", "pubkey", etc.
120    Simple(String),
121    /// Array type: { "array": ["u8", 32] }
122    Array(IdlArrayTypeSnapshot),
123    /// Option type: { "option": "u64" }
124    Option(IdlOptionTypeSnapshot),
125    /// Vec type: { "vec": "u8" }
126    Vec(IdlVecTypeSnapshot),
127    /// Defined/custom type: { "defined": { "name": "MyStruct" } }
128    Defined(IdlDefinedTypeSnapshot),
129}
130
131/// Array type representation
132#[derive(Debug, Clone, Serialize, Deserialize)]
133pub struct IdlArrayTypeSnapshot {
134    /// [element_type, size]
135    pub array: Vec<IdlArrayElementSnapshot>,
136}
137
138/// Array element (can be type or size)
139#[derive(Debug, Clone, Serialize, Deserialize)]
140#[serde(untagged)]
141pub enum IdlArrayElementSnapshot {
142    /// Nested type
143    Type(IdlTypeSnapshot),
144    /// Type name as string
145    TypeName(String),
146    /// Array size
147    Size(u32),
148}
149
150/// Option type representation
151#[derive(Debug, Clone, Serialize, Deserialize)]
152pub struct IdlOptionTypeSnapshot {
153    pub option: Box<IdlTypeSnapshot>,
154}
155
156/// Vec type representation
157#[derive(Debug, Clone, Serialize, Deserialize)]
158pub struct IdlVecTypeSnapshot {
159    pub vec: Box<IdlTypeSnapshot>,
160}
161
162/// Defined/custom type reference
163#[derive(Debug, Clone, Serialize, Deserialize)]
164pub struct IdlDefinedTypeSnapshot {
165    pub defined: IdlDefinedInnerSnapshot,
166}
167
168/// Inner defined type (can be named or simple string)
169#[derive(Debug, Clone, Serialize, Deserialize)]
170#[serde(untagged)]
171pub enum IdlDefinedInnerSnapshot {
172    /// Named: { "name": "MyStruct" }
173    Named { name: String },
174    /// Simple string reference
175    Simple(String),
176}
177
178/// Type definition (struct or enum)
179#[derive(Debug, Clone, Serialize, Deserialize)]
180pub struct IdlTypeDefSnapshot {
181    /// Type name
182    pub name: String,
183    /// Documentation
184    #[serde(default)]
185    pub docs: Vec<String>,
186    #[serde(default, skip_serializing_if = "Option::is_none")]
187    pub serialization: Option<IdlSerializationSnapshot>,
188    /// Type definition (struct or enum)
189    #[serde(rename = "type")]
190    pub type_def: IdlTypeDefKindSnapshot,
191}
192
193/// Type definition kind (struct or enum)
194#[derive(Debug, Clone, Serialize, Deserialize)]
195#[serde(untagged)]
196pub enum IdlTypeDefKindSnapshot {
197    /// Struct: { "kind": "struct", "fields": [...] }
198    Struct {
199        kind: String,
200        fields: Vec<IdlFieldSnapshot>,
201    },
202    /// Tuple struct: { "kind": "struct", "fields": ["type1", "type2"] }
203    TupleStruct {
204        kind: String,
205        fields: Vec<IdlTypeSnapshot>,
206    },
207    /// Enum: { "kind": "enum", "variants": [...] }
208    Enum {
209        kind: String,
210        variants: Vec<IdlEnumVariantSnapshot>,
211    },
212}
213
214/// Enum variant
215#[derive(Debug, Clone, Serialize, Deserialize)]
216pub struct IdlEnumVariantSnapshot {
217    pub name: String,
218}
219
220/// Event definition
221#[derive(Debug, Clone, Serialize, Deserialize)]
222pub struct IdlEventSnapshot {
223    /// Event name
224    pub name: String,
225    /// 8-byte discriminator
226    pub discriminator: Vec<u8>,
227    /// Documentation
228    #[serde(default)]
229    pub docs: Vec<String>,
230}
231
232/// Error definition
233#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
234pub struct IdlErrorSnapshot {
235    /// Error code
236    pub code: u32,
237    /// Error name
238    pub name: String,
239    /// Error message (optional - some IDLs omit this)
240    #[serde(default, skip_serializing_if = "Option::is_none")]
241    pub msg: Option<String>,
242}
243
244impl IdlTypeSnapshot {
245    /// Convert IDL type to Rust type string
246    pub fn to_rust_type_string(&self) -> String {
247        match self {
248            IdlTypeSnapshot::Simple(s) => Self::map_simple_type(s),
249            IdlTypeSnapshot::Array(arr) => {
250                if arr.array.len() == 2 {
251                    match (&arr.array[0], &arr.array[1]) {
252                        (
253                            IdlArrayElementSnapshot::TypeName(t),
254                            IdlArrayElementSnapshot::Size(size),
255                        ) => {
256                            format!("[{}; {}]", Self::map_simple_type(t), size)
257                        }
258                        (
259                            IdlArrayElementSnapshot::Type(nested),
260                            IdlArrayElementSnapshot::Size(size),
261                        ) => {
262                            format!("[{}; {}]", nested.to_rust_type_string(), size)
263                        }
264                        _ => "Vec<u8>".to_string(),
265                    }
266                } else {
267                    "Vec<u8>".to_string()
268                }
269            }
270            IdlTypeSnapshot::Option(opt) => {
271                format!("Option<{}>", opt.option.to_rust_type_string())
272            }
273            IdlTypeSnapshot::Vec(vec) => {
274                format!("Vec<{}>", vec.vec.to_rust_type_string())
275            }
276            IdlTypeSnapshot::Defined(def) => match &def.defined {
277                IdlDefinedInnerSnapshot::Named { name } => name.clone(),
278                IdlDefinedInnerSnapshot::Simple(s) => s.clone(),
279            },
280        }
281    }
282
283    fn map_simple_type(idl_type: &str) -> String {
284        match idl_type {
285            "u8" => "u8".to_string(),
286            "u16" => "u16".to_string(),
287            "u32" => "u32".to_string(),
288            "u64" => "u64".to_string(),
289            "u128" => "u128".to_string(),
290            "i8" => "i8".to_string(),
291            "i16" => "i16".to_string(),
292            "i32" => "i32".to_string(),
293            "i64" => "i64".to_string(),
294            "i128" => "i128".to_string(),
295            "bool" => "bool".to_string(),
296            "string" => "String".to_string(),
297            "publicKey" | "pubkey" => "solana_pubkey::Pubkey".to_string(),
298            "bytes" => "Vec<u8>".to_string(),
299            _ => idl_type.to_string(),
300        }
301    }
302}
303
304#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
305pub struct FieldPath {
306    pub segments: Vec<String>,
307    pub offsets: Option<Vec<usize>>,
308}
309
310impl FieldPath {
311    pub fn new(segments: &[&str]) -> Self {
312        FieldPath {
313            segments: segments.iter().map(|s| s.to_string()).collect(),
314            offsets: None,
315        }
316    }
317}
318
319#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
320pub enum Transformation {
321    HexEncode,
322    HexDecode,
323    Base58Encode,
324    Base58Decode,
325    ToString,
326    ToNumber,
327}
328
329#[derive(Debug, Clone, Serialize, Deserialize)]
330pub enum PopulationStrategy {
331    SetOnce,
332    LastWrite,
333    Append,
334    Merge,
335    Max,
336    /// Sum numeric values (accumulator pattern for aggregations)
337    Sum,
338    /// Count occurrences (increments by 1 for each update)
339    Count,
340    /// Track minimum value
341    Min,
342    /// Track unique values and store the count
343    /// Internally maintains a HashSet, exposes only the count
344    UniqueCount,
345}
346
347// ============================================================================
348// Computed Field Expression AST
349// ============================================================================
350
351/// Specification for a computed/derived field
352#[derive(Debug, Clone, Serialize, Deserialize)]
353pub struct ComputedFieldSpec {
354    /// Target field path (e.g., "trading.total_volume")
355    pub target_path: String,
356    /// Expression AST
357    pub expression: ComputedExpr,
358    /// Result type (e.g., "Option<u64>", "Option<f64>")
359    pub result_type: String,
360}
361
362// ============================================================================
363// Resolver Specifications
364// ============================================================================
365
366#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
367#[serde(rename_all = "lowercase")]
368pub enum ResolverType {
369    Token,
370}
371
372#[derive(Debug, Clone, Serialize, Deserialize)]
373pub struct ResolverExtractSpec {
374    pub target_path: String,
375    #[serde(default, skip_serializing_if = "Option::is_none")]
376    pub source_path: Option<String>,
377    #[serde(default, skip_serializing_if = "Option::is_none")]
378    pub transform: Option<Transformation>,
379}
380
381#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
382pub enum ResolveStrategy {
383    #[default]
384    SetOnce,
385    LastWrite,
386}
387
388#[derive(Debug, Clone, Serialize, Deserialize)]
389pub struct ResolverSpec {
390    pub resolver: ResolverType,
391    #[serde(default, skip_serializing_if = "Option::is_none")]
392    pub input_path: Option<String>,
393    #[serde(default, skip_serializing_if = "Option::is_none")]
394    pub input_value: Option<Value>,
395    #[serde(default)]
396    pub strategy: ResolveStrategy,
397    pub extracts: Vec<ResolverExtractSpec>,
398}
399
400/// AST for computed field expressions
401/// Supports a subset of Rust expressions needed for computed fields:
402/// - Field references (possibly from other sections)
403/// - Unwrap with defaults
404/// - Basic arithmetic and comparisons
405/// - Type casts
406/// - Method calls
407/// - Let bindings and conditionals
408/// - Byte array operations
409#[derive(Debug, Clone, Serialize, Deserialize)]
410pub enum ComputedExpr {
411    // Existing variants
412    /// Reference to a field: "field_name" or "section.field_name"
413    FieldRef {
414        path: String,
415    },
416
417    /// Unwrap with default: expr.unwrap_or(default)
418    UnwrapOr {
419        expr: Box<ComputedExpr>,
420        default: serde_json::Value,
421    },
422
423    /// Binary operation: left op right
424    Binary {
425        op: BinaryOp,
426        left: Box<ComputedExpr>,
427        right: Box<ComputedExpr>,
428    },
429
430    /// Type cast: expr as type
431    Cast {
432        expr: Box<ComputedExpr>,
433        to_type: String,
434    },
435
436    /// Method call: expr.method(args)
437    MethodCall {
438        expr: Box<ComputedExpr>,
439        method: String,
440        args: Vec<ComputedExpr>,
441    },
442
443    /// Computation provided by a resolver
444    ResolverComputed {
445        resolver: String,
446        method: String,
447        args: Vec<ComputedExpr>,
448    },
449
450    /// Literal value: numbers, booleans, strings
451    Literal {
452        value: serde_json::Value,
453    },
454
455    /// Parenthesized expression for grouping
456    Paren {
457        expr: Box<ComputedExpr>,
458    },
459
460    // Variable reference (for let bindings)
461    Var {
462        name: String,
463    },
464
465    // Let binding: let name = value; body
466    Let {
467        name: String,
468        value: Box<ComputedExpr>,
469        body: Box<ComputedExpr>,
470    },
471
472    // Conditional: if condition { then_branch } else { else_branch }
473    If {
474        condition: Box<ComputedExpr>,
475        then_branch: Box<ComputedExpr>,
476        else_branch: Box<ComputedExpr>,
477    },
478
479    // Option constructors
480    None,
481    Some {
482        value: Box<ComputedExpr>,
483    },
484
485    // Byte/array operations
486    Slice {
487        expr: Box<ComputedExpr>,
488        start: usize,
489        end: usize,
490    },
491    Index {
492        expr: Box<ComputedExpr>,
493        index: usize,
494    },
495
496    // Byte conversion functions
497    U64FromLeBytes {
498        bytes: Box<ComputedExpr>,
499    },
500    U64FromBeBytes {
501        bytes: Box<ComputedExpr>,
502    },
503
504    // Byte array literals: [0u8; 32] or [1, 2, 3]
505    ByteArray {
506        bytes: Vec<u8>,
507    },
508
509    // Closure for map operations: |x| body
510    Closure {
511        param: String,
512        body: Box<ComputedExpr>,
513    },
514
515    // Unary operations
516    Unary {
517        op: UnaryOp,
518        expr: Box<ComputedExpr>,
519    },
520
521    // JSON array to bytes conversion (for working with captured byte arrays)
522    JsonToBytes {
523        expr: Box<ComputedExpr>,
524    },
525
526    // Context access - slot and timestamp from the update that triggered evaluation
527    /// Access the slot number from the current update context
528    ContextSlot,
529    /// Access the unix timestamp from the current update context
530    ContextTimestamp,
531}
532
533/// Binary operators for computed expressions
534#[derive(Debug, Clone, Serialize, Deserialize)]
535pub enum BinaryOp {
536    // Arithmetic
537    Add,
538    Sub,
539    Mul,
540    Div,
541    Mod,
542    // Comparison
543    Gt,
544    Lt,
545    Gte,
546    Lte,
547    Eq,
548    Ne,
549    // Logical
550    And,
551    Or,
552    // Bitwise
553    Xor,
554    BitAnd,
555    BitOr,
556    Shl,
557    Shr,
558}
559
560/// Unary operators for computed expressions
561#[derive(Debug, Clone, Serialize, Deserialize)]
562pub enum UnaryOp {
563    Not,
564    ReverseBits,
565}
566
567/// Serializable version of StreamSpec without phantom types
568#[derive(Debug, Clone, Serialize, Deserialize)]
569pub struct SerializableStreamSpec {
570    pub state_name: String,
571    /// Program ID (Solana address) - extracted from IDL
572    #[serde(default)]
573    pub program_id: Option<String>,
574    /// Embedded IDL for AST-only compilation
575    #[serde(default)]
576    pub idl: Option<IdlSnapshot>,
577    pub identity: IdentitySpec,
578    pub handlers: Vec<SerializableHandlerSpec>,
579    pub sections: Vec<EntitySection>,
580    pub field_mappings: BTreeMap<String, FieldTypeInfo>,
581    pub resolver_hooks: Vec<ResolverHook>,
582    pub instruction_hooks: Vec<InstructionHook>,
583    #[serde(default)]
584    pub resolver_specs: Vec<ResolverSpec>,
585    /// Computed field paths (legacy, for backward compatibility)
586    #[serde(default)]
587    pub computed_fields: Vec<String>,
588    /// Computed field specifications with full expression AST
589    #[serde(default)]
590    pub computed_field_specs: Vec<ComputedFieldSpec>,
591    /// Deterministic content hash (SHA256 of canonical JSON, excluding this field)
592    /// Used for deduplication and version tracking
593    #[serde(default, skip_serializing_if = "Option::is_none")]
594    pub content_hash: Option<String>,
595    /// View definitions for derived/projected views
596    #[serde(default)]
597    pub views: Vec<ViewDef>,
598}
599
600#[derive(Debug, Clone)]
601pub struct TypedStreamSpec<S> {
602    pub state_name: String,
603    pub identity: IdentitySpec,
604    pub handlers: Vec<TypedHandlerSpec<S>>,
605    pub sections: Vec<EntitySection>, // NEW: Complete structural information
606    pub field_mappings: BTreeMap<String, FieldTypeInfo>, // NEW: All field type info by target path
607    pub resolver_hooks: Vec<ResolverHook>, // NEW: Resolver hooks for PDA key resolution
608    pub instruction_hooks: Vec<InstructionHook>, // NEW: Instruction hooks for PDA registration
609    pub resolver_specs: Vec<ResolverSpec>,
610    pub computed_fields: Vec<String>, // List of computed field paths
611    _phantom: PhantomData<S>,
612}
613
614impl<S> TypedStreamSpec<S> {
615    pub fn new(
616        state_name: String,
617        identity: IdentitySpec,
618        handlers: Vec<TypedHandlerSpec<S>>,
619    ) -> Self {
620        TypedStreamSpec {
621            state_name,
622            identity,
623            handlers,
624            sections: Vec::new(),
625            field_mappings: BTreeMap::new(),
626            resolver_hooks: Vec::new(),
627            instruction_hooks: Vec::new(),
628            resolver_specs: Vec::new(),
629            computed_fields: Vec::new(),
630            _phantom: PhantomData,
631        }
632    }
633
634    /// Enhanced constructor with type information
635    pub fn with_type_info(
636        state_name: String,
637        identity: IdentitySpec,
638        handlers: Vec<TypedHandlerSpec<S>>,
639        sections: Vec<EntitySection>,
640        field_mappings: BTreeMap<String, FieldTypeInfo>,
641    ) -> Self {
642        TypedStreamSpec {
643            state_name,
644            identity,
645            handlers,
646            sections,
647            field_mappings,
648            resolver_hooks: Vec::new(),
649            instruction_hooks: Vec::new(),
650            resolver_specs: Vec::new(),
651            computed_fields: Vec::new(),
652            _phantom: PhantomData,
653        }
654    }
655
656    pub fn with_resolver_specs(mut self, resolver_specs: Vec<ResolverSpec>) -> Self {
657        self.resolver_specs = resolver_specs;
658        self
659    }
660
661    /// Get type information for a specific field path
662    pub fn get_field_type(&self, path: &str) -> Option<&FieldTypeInfo> {
663        self.field_mappings.get(path)
664    }
665
666    /// Get all fields for a specific section
667    pub fn get_section_fields(&self, section_name: &str) -> Option<&Vec<FieldTypeInfo>> {
668        self.sections
669            .iter()
670            .find(|s| s.name == section_name)
671            .map(|s| &s.fields)
672    }
673
674    /// Get all section names
675    pub fn get_section_names(&self) -> Vec<&String> {
676        self.sections.iter().map(|s| &s.name).collect()
677    }
678
679    /// Convert to serializable format
680    pub fn to_serializable(&self) -> SerializableStreamSpec {
681        let mut spec = SerializableStreamSpec {
682            state_name: self.state_name.clone(),
683            program_id: None,
684            idl: None,
685            identity: self.identity.clone(),
686            handlers: self.handlers.iter().map(|h| h.to_serializable()).collect(),
687            sections: self.sections.clone(),
688            field_mappings: self.field_mappings.clone(),
689            resolver_hooks: self.resolver_hooks.clone(),
690            instruction_hooks: self.instruction_hooks.clone(),
691            resolver_specs: self.resolver_specs.clone(),
692            computed_fields: self.computed_fields.clone(),
693            computed_field_specs: Vec::new(),
694            content_hash: None,
695            views: Vec::new(),
696        };
697        spec.content_hash = Some(spec.compute_content_hash());
698        spec
699    }
700
701    /// Create from serializable format
702    pub fn from_serializable(spec: SerializableStreamSpec) -> Self {
703        TypedStreamSpec {
704            state_name: spec.state_name,
705            identity: spec.identity,
706            handlers: spec
707                .handlers
708                .into_iter()
709                .map(|h| TypedHandlerSpec::from_serializable(h))
710                .collect(),
711            sections: spec.sections,
712            field_mappings: spec.field_mappings,
713            resolver_hooks: spec.resolver_hooks,
714            instruction_hooks: spec.instruction_hooks,
715            resolver_specs: spec.resolver_specs,
716            computed_fields: spec.computed_fields,
717            _phantom: PhantomData,
718        }
719    }
720}
721
722#[derive(Debug, Clone, Serialize, Deserialize)]
723pub struct IdentitySpec {
724    pub primary_keys: Vec<String>,
725    pub lookup_indexes: Vec<LookupIndexSpec>,
726}
727
728#[derive(Debug, Clone, Serialize, Deserialize)]
729pub struct LookupIndexSpec {
730    pub field_name: String,
731    pub temporal_field: Option<String>,
732}
733
734// ============================================================================
735// Level 1: Declarative Hook Extensions
736// ============================================================================
737
738/// Declarative resolver hook specification
739#[derive(Debug, Clone, Serialize, Deserialize)]
740pub struct ResolverHook {
741    /// Account type this resolver applies to (e.g., "BondingCurveState")
742    pub account_type: String,
743
744    /// Resolution strategy
745    pub strategy: ResolverStrategy,
746}
747
748#[derive(Debug, Clone, Serialize, Deserialize)]
749pub enum ResolverStrategy {
750    /// Look up PDA in reverse lookup table, queue if not found
751    PdaReverseLookup {
752        lookup_name: String,
753        /// Instruction discriminators to queue until (8 bytes each)
754        queue_discriminators: Vec<Vec<u8>>,
755    },
756
757    /// Extract primary key directly from account data (future)
758    DirectField { field_path: FieldPath },
759}
760
761/// Declarative instruction hook specification
762#[derive(Debug, Clone, Serialize, Deserialize)]
763pub struct InstructionHook {
764    /// Instruction type this hook applies to (e.g., "CreateIxState")
765    pub instruction_type: String,
766
767    /// Actions to perform when this instruction is processed
768    pub actions: Vec<HookAction>,
769
770    /// Lookup strategy for finding the entity
771    pub lookup_by: Option<FieldPath>,
772}
773
774#[derive(Debug, Clone, Serialize, Deserialize)]
775pub enum HookAction {
776    /// Register a PDA mapping for reverse lookup
777    RegisterPdaMapping {
778        pda_field: FieldPath,
779        seed_field: FieldPath,
780        lookup_name: String,
781    },
782
783    /// Set a field value (for #[track_from])
784    SetField {
785        target_field: String,
786        source: MappingSource,
787        condition: Option<ConditionExpr>,
788    },
789
790    /// Increment a field value (for conditional aggregations)
791    IncrementField {
792        target_field: String,
793        increment_by: i64,
794        condition: Option<ConditionExpr>,
795    },
796}
797
798/// Simple condition expression (Level 1 - basic comparisons only)
799#[derive(Debug, Clone, Serialize, Deserialize)]
800pub struct ConditionExpr {
801    /// Expression as string (will be parsed and validated)
802    pub expression: String,
803
804    /// Parsed representation (for validation and execution)
805    pub parsed: Option<ParsedCondition>,
806}
807
808#[derive(Debug, Clone, Serialize, Deserialize)]
809pub enum ParsedCondition {
810    /// Binary comparison: field op value
811    Comparison {
812        field: FieldPath,
813        op: ComparisonOp,
814        value: serde_json::Value,
815    },
816
817    /// Logical AND/OR
818    Logical {
819        op: LogicalOp,
820        conditions: Vec<ParsedCondition>,
821    },
822}
823
824#[derive(Debug, Clone, Serialize, Deserialize)]
825pub enum ComparisonOp {
826    Equal,
827    NotEqual,
828    GreaterThan,
829    GreaterThanOrEqual,
830    LessThan,
831    LessThanOrEqual,
832}
833
834#[derive(Debug, Clone, Serialize, Deserialize)]
835pub enum LogicalOp {
836    And,
837    Or,
838}
839
840/// Serializable version of HandlerSpec without phantom types
841#[derive(Debug, Clone, Serialize, Deserialize)]
842pub struct SerializableHandlerSpec {
843    pub source: SourceSpec,
844    pub key_resolution: KeyResolutionStrategy,
845    pub mappings: Vec<SerializableFieldMapping>,
846    pub conditions: Vec<Condition>,
847    pub emit: bool,
848}
849
850#[derive(Debug, Clone)]
851pub struct TypedHandlerSpec<S> {
852    pub source: SourceSpec,
853    pub key_resolution: KeyResolutionStrategy,
854    pub mappings: Vec<TypedFieldMapping<S>>,
855    pub conditions: Vec<Condition>,
856    pub emit: bool,
857    _phantom: PhantomData<S>,
858}
859
860impl<S> TypedHandlerSpec<S> {
861    pub fn new(
862        source: SourceSpec,
863        key_resolution: KeyResolutionStrategy,
864        mappings: Vec<TypedFieldMapping<S>>,
865        emit: bool,
866    ) -> Self {
867        TypedHandlerSpec {
868            source,
869            key_resolution,
870            mappings,
871            conditions: vec![],
872            emit,
873            _phantom: PhantomData,
874        }
875    }
876
877    /// Convert to serializable format
878    pub fn to_serializable(&self) -> SerializableHandlerSpec {
879        SerializableHandlerSpec {
880            source: self.source.clone(),
881            key_resolution: self.key_resolution.clone(),
882            mappings: self.mappings.iter().map(|m| m.to_serializable()).collect(),
883            conditions: self.conditions.clone(),
884            emit: self.emit,
885        }
886    }
887
888    /// Create from serializable format
889    pub fn from_serializable(spec: SerializableHandlerSpec) -> Self {
890        TypedHandlerSpec {
891            source: spec.source,
892            key_resolution: spec.key_resolution,
893            mappings: spec
894                .mappings
895                .into_iter()
896                .map(|m| TypedFieldMapping::from_serializable(m))
897                .collect(),
898            conditions: spec.conditions,
899            emit: spec.emit,
900            _phantom: PhantomData,
901        }
902    }
903}
904
905#[derive(Debug, Clone, Serialize, Deserialize)]
906pub enum KeyResolutionStrategy {
907    Embedded {
908        primary_field: FieldPath,
909    },
910    Lookup {
911        primary_field: FieldPath,
912    },
913    Computed {
914        primary_field: FieldPath,
915        compute_partition: ComputeFunction,
916    },
917    TemporalLookup {
918        lookup_field: FieldPath,
919        timestamp_field: FieldPath,
920        index_name: String,
921    },
922}
923
924#[derive(Debug, Clone, Serialize, Deserialize)]
925pub enum SourceSpec {
926    Source {
927        program_id: Option<String>,
928        discriminator: Option<Vec<u8>>,
929        type_name: String,
930        #[serde(default, skip_serializing_if = "Option::is_none")]
931        serialization: Option<IdlSerializationSnapshot>,
932    },
933}
934
935/// Serializable version of FieldMapping without phantom types
936#[derive(Debug, Clone, Serialize, Deserialize)]
937pub struct SerializableFieldMapping {
938    pub target_path: String,
939    pub source: MappingSource,
940    pub transform: Option<Transformation>,
941    pub population: PopulationStrategy,
942    #[serde(default, skip_serializing_if = "Option::is_none")]
943    pub condition: Option<ConditionExpr>,
944    #[serde(default, skip_serializing_if = "Option::is_none")]
945    pub when: Option<String>,
946    #[serde(default, skip_serializing_if = "Option::is_none")]
947    pub stop: Option<String>,
948    #[serde(default = "default_emit", skip_serializing_if = "is_true")]
949    pub emit: bool,
950}
951
952fn default_emit() -> bool {
953    true
954}
955
956fn is_true(value: &bool) -> bool {
957    *value
958}
959
960#[derive(Debug, Clone)]
961pub struct TypedFieldMapping<S> {
962    pub target_path: String,
963    pub source: MappingSource,
964    pub transform: Option<Transformation>,
965    pub population: PopulationStrategy,
966    pub condition: Option<ConditionExpr>,
967    pub when: Option<String>,
968    pub stop: Option<String>,
969    pub emit: bool,
970    _phantom: PhantomData<S>,
971}
972
973impl<S> TypedFieldMapping<S> {
974    pub fn new(target_path: String, source: MappingSource, population: PopulationStrategy) -> Self {
975        TypedFieldMapping {
976            target_path,
977            source,
978            transform: None,
979            population,
980            condition: None,
981            when: None,
982            stop: None,
983            emit: true,
984            _phantom: PhantomData,
985        }
986    }
987
988    pub fn with_transform(mut self, transform: Transformation) -> Self {
989        self.transform = Some(transform);
990        self
991    }
992
993    pub fn with_condition(mut self, condition: ConditionExpr) -> Self {
994        self.condition = Some(condition);
995        self
996    }
997
998    pub fn with_when(mut self, when: String) -> Self {
999        self.when = Some(when);
1000        self
1001    }
1002
1003    pub fn with_stop(mut self, stop: String) -> Self {
1004        self.stop = Some(stop);
1005        self
1006    }
1007
1008    pub fn with_emit(mut self, emit: bool) -> Self {
1009        self.emit = emit;
1010        self
1011    }
1012
1013    /// Convert to serializable format
1014    pub fn to_serializable(&self) -> SerializableFieldMapping {
1015        SerializableFieldMapping {
1016            target_path: self.target_path.clone(),
1017            source: self.source.clone(),
1018            transform: self.transform.clone(),
1019            population: self.population.clone(),
1020            condition: self.condition.clone(),
1021            when: self.when.clone(),
1022            stop: self.stop.clone(),
1023            emit: self.emit,
1024        }
1025    }
1026
1027    /// Create from serializable format
1028    pub fn from_serializable(mapping: SerializableFieldMapping) -> Self {
1029        TypedFieldMapping {
1030            target_path: mapping.target_path,
1031            source: mapping.source,
1032            transform: mapping.transform,
1033            population: mapping.population,
1034            condition: mapping.condition,
1035            when: mapping.when,
1036            stop: mapping.stop,
1037            emit: mapping.emit,
1038            _phantom: PhantomData,
1039        }
1040    }
1041}
1042
1043#[derive(Debug, Clone, Serialize, Deserialize)]
1044pub enum MappingSource {
1045    FromSource {
1046        path: FieldPath,
1047        default: Option<Value>,
1048        transform: Option<Transformation>,
1049    },
1050    Constant(Value),
1051    Computed {
1052        inputs: Vec<FieldPath>,
1053        function: ComputeFunction,
1054    },
1055    FromState {
1056        path: String,
1057    },
1058    AsEvent {
1059        fields: Vec<Box<MappingSource>>,
1060    },
1061    WholeSource,
1062    /// Similar to WholeSource but with field-level transformations
1063    /// Used by #[capture] macro to apply transforms to specific fields in an account
1064    AsCapture {
1065        field_transforms: BTreeMap<String, Transformation>,
1066    },
1067    /// From instruction context (timestamp, slot, signature)
1068    /// Used by #[track_from] with special fields like __timestamp
1069    FromContext {
1070        field: String,
1071    },
1072}
1073
1074impl MappingSource {
1075    pub fn with_transform(self, transform: Transformation) -> Self {
1076        match self {
1077            MappingSource::FromSource {
1078                path,
1079                default,
1080                transform: _,
1081            } => MappingSource::FromSource {
1082                path,
1083                default,
1084                transform: Some(transform),
1085            },
1086            other => other,
1087        }
1088    }
1089}
1090
1091#[derive(Debug, Clone, Serialize, Deserialize)]
1092pub enum ComputeFunction {
1093    Sum,
1094    Concat,
1095    Format(String),
1096    Custom(String),
1097}
1098
1099#[derive(Debug, Clone, Serialize, Deserialize)]
1100pub struct Condition {
1101    pub field: FieldPath,
1102    pub operator: ConditionOp,
1103    pub value: Value,
1104}
1105
1106#[derive(Debug, Clone, Serialize, Deserialize)]
1107pub enum ConditionOp {
1108    Equals,
1109    NotEquals,
1110    GreaterThan,
1111    LessThan,
1112    Contains,
1113    Exists,
1114}
1115
1116/// Language-agnostic type information for fields
1117#[derive(Debug, Clone, Serialize, Deserialize)]
1118pub struct FieldTypeInfo {
1119    pub field_name: String,
1120    pub rust_type_name: String, // Full Rust type: "Option<i64>", "Vec<Value>", etc.
1121    pub base_type: BaseType,    // Fundamental type classification
1122    pub is_optional: bool,      // true for Option<T>
1123    pub is_array: bool,         // true for Vec<T>
1124    pub inner_type: Option<String>, // For Option<T> or Vec<T>, store the inner type
1125    pub source_path: Option<String>, // Path to source field if this is mapped
1126    /// Resolved type information for complex types (instructions, accounts, custom types)
1127    #[serde(default)]
1128    pub resolved_type: Option<ResolvedStructType>,
1129    #[serde(default = "default_emit", skip_serializing_if = "is_true")]
1130    pub emit: bool,
1131}
1132
1133/// Resolved structure type with field information from IDL
1134#[derive(Debug, Clone, Serialize, Deserialize)]
1135pub struct ResolvedStructType {
1136    pub type_name: String,
1137    pub fields: Vec<ResolvedField>,
1138    pub is_instruction: bool,
1139    pub is_account: bool,
1140    pub is_event: bool,
1141    /// If true, this is an enum type and enum_variants should be used instead of fields
1142    #[serde(default)]
1143    pub is_enum: bool,
1144    /// For enum types, list of variant names
1145    #[serde(default)]
1146    pub enum_variants: Vec<String>,
1147}
1148
1149/// A resolved field within a complex type
1150#[derive(Debug, Clone, Serialize, Deserialize)]
1151pub struct ResolvedField {
1152    pub field_name: String,
1153    pub field_type: String,
1154    pub base_type: BaseType,
1155    pub is_optional: bool,
1156    pub is_array: bool,
1157}
1158
1159/// Language-agnostic base type classification
1160#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1161pub enum BaseType {
1162    // Numeric types
1163    Integer, // i8, i16, i32, i64, u8, u16, u32, u64, usize, isize
1164    Float,   // f32, f64
1165    // Text types
1166    String, // String, &str
1167    // Boolean
1168    Boolean, // bool
1169    // Complex types
1170    Object, // Custom structs, HashMap, etc.
1171    Array,  // Vec<T>, arrays
1172    Binary, // Bytes, binary data
1173    // Special types
1174    Timestamp, // Detected from field names ending in _at, _time, etc.
1175    Pubkey,    // Solana public key (Base58 encoded)
1176    Any,       // serde_json::Value, unknown types
1177}
1178
1179/// Represents a logical section/group of fields in the entity
1180#[derive(Debug, Clone, Serialize, Deserialize)]
1181pub struct EntitySection {
1182    pub name: String,
1183    pub fields: Vec<FieldTypeInfo>,
1184    pub is_nested_struct: bool,
1185    pub parent_field: Option<String>, // If this section comes from a nested struct field
1186}
1187
1188impl FieldTypeInfo {
1189    pub fn new(field_name: String, rust_type_name: String) -> Self {
1190        let (base_type, is_optional, is_array, inner_type) =
1191            Self::analyze_rust_type(&rust_type_name);
1192
1193        FieldTypeInfo {
1194            field_name: field_name.clone(),
1195            rust_type_name,
1196            base_type: Self::infer_semantic_type(&field_name, base_type),
1197            is_optional,
1198            is_array,
1199            inner_type,
1200            source_path: None,
1201            resolved_type: None,
1202            emit: true,
1203        }
1204    }
1205
1206    pub fn with_source_path(mut self, source_path: String) -> Self {
1207        self.source_path = Some(source_path);
1208        self
1209    }
1210
1211    /// Analyze a Rust type string and extract structural information
1212    fn analyze_rust_type(rust_type: &str) -> (BaseType, bool, bool, Option<String>) {
1213        let type_str = rust_type.trim();
1214
1215        // Handle Option<T>
1216        if let Some(inner) = Self::extract_generic_inner(type_str, "Option") {
1217            let (inner_base_type, _, inner_is_array, inner_inner_type) =
1218                Self::analyze_rust_type(&inner);
1219            return (
1220                inner_base_type,
1221                true,
1222                inner_is_array,
1223                inner_inner_type.or(Some(inner)),
1224            );
1225        }
1226
1227        // Handle Vec<T>
1228        if let Some(inner) = Self::extract_generic_inner(type_str, "Vec") {
1229            let (_inner_base_type, inner_is_optional, _, inner_inner_type) =
1230                Self::analyze_rust_type(&inner);
1231            return (
1232                BaseType::Array,
1233                inner_is_optional,
1234                true,
1235                inner_inner_type.or(Some(inner)),
1236            );
1237        }
1238
1239        // Handle primitive types
1240        let base_type = match type_str {
1241            "i8" | "i16" | "i32" | "i64" | "isize" | "u8" | "u16" | "u32" | "u64" | "usize" => {
1242                BaseType::Integer
1243            }
1244            "f32" | "f64" => BaseType::Float,
1245            "bool" => BaseType::Boolean,
1246            "String" | "&str" | "str" => BaseType::String,
1247            "Value" | "serde_json::Value" => BaseType::Any,
1248            "Pubkey" | "solana_pubkey::Pubkey" => BaseType::Pubkey,
1249            _ => {
1250                // Check for binary types
1251                if type_str.contains("Bytes") || type_str.contains("bytes") {
1252                    BaseType::Binary
1253                } else if type_str.contains("Pubkey") {
1254                    BaseType::Pubkey
1255                } else {
1256                    BaseType::Object
1257                }
1258            }
1259        };
1260
1261        (base_type, false, false, None)
1262    }
1263
1264    /// Extract inner type from generic like "Option<T>" -> "T"
1265    fn extract_generic_inner(type_str: &str, generic_name: &str) -> Option<String> {
1266        let pattern = format!("{}<", generic_name);
1267        if type_str.starts_with(&pattern) && type_str.ends_with('>') {
1268            let start = pattern.len();
1269            let end = type_str.len() - 1;
1270            if end > start {
1271                return Some(type_str[start..end].trim().to_string());
1272            }
1273        }
1274        None
1275    }
1276
1277    /// Infer semantic type based on field name patterns
1278    fn infer_semantic_type(field_name: &str, base_type: BaseType) -> BaseType {
1279        let lower_name = field_name.to_lowercase();
1280
1281        // If already classified as integer, check if it should be timestamp
1282        if base_type == BaseType::Integer
1283            && (lower_name.ends_with("_at")
1284                || lower_name.ends_with("_time")
1285                || lower_name.contains("timestamp")
1286                || lower_name.contains("created")
1287                || lower_name.contains("settled")
1288                || lower_name.contains("activated"))
1289        {
1290            return BaseType::Timestamp;
1291        }
1292
1293        base_type
1294    }
1295}
1296
1297pub trait FieldAccessor<S> {
1298    fn path(&self) -> String;
1299}
1300
1301// ============================================================================
1302// SerializableStreamSpec Implementation
1303// ============================================================================
1304
1305impl SerializableStreamSpec {
1306    /// Compute deterministic content hash (SHA256 of canonical JSON).
1307    ///
1308    /// The hash is computed over the entire spec except the content_hash field itself,
1309    /// ensuring the same AST always produces the same hash regardless of when it was
1310    /// generated or by whom.
1311    pub fn compute_content_hash(&self) -> String {
1312        use sha2::{Digest, Sha256};
1313
1314        // Clone and clear the hash field for computation
1315        let mut spec_for_hash = self.clone();
1316        spec_for_hash.content_hash = None;
1317
1318        // Serialize to JSON (serde_json produces consistent output for the same struct)
1319        let json =
1320            serde_json::to_string(&spec_for_hash).expect("Failed to serialize spec for hashing");
1321
1322        // Compute SHA256 hash
1323        let mut hasher = Sha256::new();
1324        hasher.update(json.as_bytes());
1325        let result = hasher.finalize();
1326
1327        // Return hex-encoded hash
1328        hex::encode(result)
1329    }
1330
1331    /// Verify that the content_hash matches the computed hash.
1332    /// Returns true if hash is valid or not set.
1333    pub fn verify_content_hash(&self) -> bool {
1334        match &self.content_hash {
1335            Some(hash) => {
1336                let computed = self.compute_content_hash();
1337                hash == &computed
1338            }
1339            None => true, // No hash to verify
1340        }
1341    }
1342
1343    /// Set the content_hash field to the computed hash.
1344    pub fn with_content_hash(mut self) -> Self {
1345        self.content_hash = Some(self.compute_content_hash());
1346        self
1347    }
1348}
1349
1350// ============================================================================
1351// PDA and Instruction Types — For SDK code generation
1352// ============================================================================
1353
1354/// PDA (Program-Derived Address) definition for the stack-level registry.
1355/// PDAs defined here can be referenced by instructions via `pdaRef`.
1356#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1357pub struct PdaDefinition {
1358    /// Human-readable name (e.g., "miner", "bondingCurve")
1359    pub name: String,
1360
1361    /// Seeds for PDA derivation, in order
1362    pub seeds: Vec<PdaSeedDef>,
1363
1364    /// Program ID that owns this PDA.
1365    /// If None, uses the stack's primary programId.
1366    #[serde(default, skip_serializing_if = "Option::is_none")]
1367    pub program_id: Option<String>,
1368}
1369
1370/// Single seed in a PDA derivation.
1371#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1372#[serde(tag = "type", rename_all = "camelCase")]
1373pub enum PdaSeedDef {
1374    /// Static string seed: "miner" → "miner".as_bytes()
1375    Literal { value: String },
1376
1377    /// Static byte array (for non-UTF8 seeds)
1378    Bytes { value: Vec<u8> },
1379
1380    /// Reference to an instruction argument: arg("roundId") → args.roundId as bytes
1381    ArgRef {
1382        arg_name: String,
1383        /// Optional type hint for serialization (e.g., "u64", "pubkey")
1384        #[serde(default, skip_serializing_if = "Option::is_none")]
1385        arg_type: Option<String>,
1386    },
1387
1388    /// Reference to another account in the instruction: account("mint") → accounts.mint pubkey
1389    AccountRef { account_name: String },
1390}
1391
1392/// How an instruction account's address is determined.
1393#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1394#[serde(tag = "category", rename_all = "camelCase")]
1395pub enum AccountResolution {
1396    /// Must sign the transaction (uses wallet.publicKey)
1397    Signer,
1398
1399    /// Fixed known address (e.g., System Program, Token Program)
1400    Known { address: String },
1401
1402    /// Reference to a PDA in the stack's pdas registry
1403    PdaRef { pda_name: String },
1404
1405    /// Inline PDA definition (for one-off PDAs not in the registry)
1406    PdaInline {
1407        seeds: Vec<PdaSeedDef>,
1408        #[serde(default, skip_serializing_if = "Option::is_none")]
1409        program_id: Option<String>,
1410    },
1411
1412    /// User must provide at call time via options.accounts
1413    UserProvided,
1414}
1415
1416/// Account metadata for an instruction.
1417#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1418pub struct InstructionAccountDef {
1419    /// Account name (e.g., "user", "mint", "bondingCurve")
1420    pub name: String,
1421
1422    /// Whether this account must sign the transaction
1423    #[serde(default)]
1424    pub is_signer: bool,
1425
1426    /// Whether this account is writable
1427    #[serde(default)]
1428    pub is_writable: bool,
1429
1430    /// How this account's address is resolved
1431    pub resolution: AccountResolution,
1432
1433    /// Whether this account can be omitted (optional accounts)
1434    #[serde(default)]
1435    pub is_optional: bool,
1436
1437    /// Documentation from IDL
1438    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1439    pub docs: Vec<String>,
1440}
1441
1442/// Argument definition for an instruction.
1443#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1444pub struct InstructionArgDef {
1445    /// Argument name
1446    pub name: String,
1447
1448    /// Type from IDL (e.g., "u64", "bool", "pubkey", "Option<u64>")
1449    #[serde(rename = "type")]
1450    pub arg_type: String,
1451
1452    /// Documentation from IDL
1453    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1454    pub docs: Vec<String>,
1455}
1456
1457/// Full instruction definition in the AST.
1458#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1459pub struct InstructionDef {
1460    /// Instruction name (e.g., "buy", "sell", "automate")
1461    pub name: String,
1462
1463    /// Discriminator bytes (8 bytes for Anchor, 1 byte for Steel)
1464    pub discriminator: Vec<u8>,
1465
1466    /// Size of discriminator in bytes (for buffer allocation)
1467    #[serde(default = "default_discriminant_size")]
1468    pub discriminator_size: usize,
1469
1470    /// Accounts required by this instruction, in order
1471    pub accounts: Vec<InstructionAccountDef>,
1472
1473    /// Arguments for this instruction, in order
1474    pub args: Vec<InstructionArgDef>,
1475
1476    /// Error definitions specific to this instruction
1477    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1478    pub errors: Vec<IdlErrorSnapshot>,
1479
1480    /// Program ID for this instruction (usually same as stack's programId)
1481    #[serde(default, skip_serializing_if = "Option::is_none")]
1482    pub program_id: Option<String>,
1483
1484    /// Documentation from IDL
1485    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1486    pub docs: Vec<String>,
1487}
1488
1489// ============================================================================
1490// Stack Spec — Unified multi-entity AST format
1491// ============================================================================
1492
1493/// A unified stack specification containing all entities.
1494/// Written to `.hyperstack/{StackName}.stack.json`.
1495#[derive(Debug, Clone, Serialize, Deserialize)]
1496pub struct SerializableStackSpec {
1497    /// Stack name (PascalCase, derived from module ident)
1498    pub stack_name: String,
1499    /// Program IDs (one per IDL, in order)
1500    #[serde(default)]
1501    pub program_ids: Vec<String>,
1502    /// IDL snapshots (one per program)
1503    #[serde(default)]
1504    pub idls: Vec<IdlSnapshot>,
1505    /// All entity specifications in this stack
1506    pub entities: Vec<SerializableStreamSpec>,
1507    /// PDA registry - defines all PDAs for the stack, grouped by program name
1508    /// Outer key is program name (e.g., "ore", "entropy"), inner key is PDA name
1509    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
1510    pub pdas: BTreeMap<String, BTreeMap<String, PdaDefinition>>,
1511    /// Instruction definitions for SDK code generation
1512    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1513    pub instructions: Vec<InstructionDef>,
1514    /// Deterministic content hash of the entire stack
1515    #[serde(default, skip_serializing_if = "Option::is_none")]
1516    pub content_hash: Option<String>,
1517}
1518
1519impl SerializableStackSpec {
1520    /// Compute deterministic content hash (SHA256 of canonical JSON).
1521    pub fn compute_content_hash(&self) -> String {
1522        use sha2::{Digest, Sha256};
1523        let mut spec_for_hash = self.clone();
1524        spec_for_hash.content_hash = None;
1525        let json = serde_json::to_string(&spec_for_hash)
1526            .expect("Failed to serialize stack spec for hashing");
1527        let mut hasher = Sha256::new();
1528        hasher.update(json.as_bytes());
1529        hex::encode(hasher.finalize())
1530    }
1531
1532    pub fn with_content_hash(mut self) -> Self {
1533        self.content_hash = Some(self.compute_content_hash());
1534        self
1535    }
1536}
1537
1538// ============================================================================
1539// View Pipeline Types - Composable View Definitions
1540// ============================================================================
1541
1542/// Sort order for view transforms
1543#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)]
1544#[serde(rename_all = "lowercase")]
1545pub enum SortOrder {
1546    #[default]
1547    Asc,
1548    Desc,
1549}
1550
1551/// Comparison operators for predicates
1552#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
1553pub enum CompareOp {
1554    Eq,
1555    Ne,
1556    Gt,
1557    Gte,
1558    Lt,
1559    Lte,
1560}
1561
1562/// Value in a predicate comparison
1563#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1564pub enum PredicateValue {
1565    /// Literal JSON value
1566    Literal(serde_json::Value),
1567    /// Dynamic runtime value (e.g., "now()" for current timestamp)
1568    Dynamic(String),
1569    /// Reference to another field
1570    Field(FieldPath),
1571}
1572
1573/// Predicate for filtering entities
1574#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1575pub enum Predicate {
1576    /// Field comparison: field op value
1577    Compare {
1578        field: FieldPath,
1579        op: CompareOp,
1580        value: PredicateValue,
1581    },
1582    /// Logical AND of predicates
1583    And(Vec<Predicate>),
1584    /// Logical OR of predicates
1585    Or(Vec<Predicate>),
1586    /// Negation
1587    Not(Box<Predicate>),
1588    /// Field exists (is not null)
1589    Exists { field: FieldPath },
1590}
1591
1592/// Transform operation in a view pipeline
1593#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1594pub enum ViewTransform {
1595    /// Filter entities matching a predicate
1596    Filter { predicate: Predicate },
1597
1598    /// Sort entities by a field
1599    Sort {
1600        key: FieldPath,
1601        #[serde(default)]
1602        order: SortOrder,
1603    },
1604
1605    /// Take first N entities (after sort)
1606    Take { count: usize },
1607
1608    /// Skip first N entities
1609    Skip { count: usize },
1610
1611    /// Take only the first entity (after sort) - produces Single output
1612    First,
1613
1614    /// Take only the last entity (after sort) - produces Single output
1615    Last,
1616
1617    /// Get entity with maximum value for field - produces Single output
1618    MaxBy { key: FieldPath },
1619
1620    /// Get entity with minimum value for field - produces Single output
1621    MinBy { key: FieldPath },
1622}
1623
1624/// Source for a view definition
1625#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1626pub enum ViewSource {
1627    /// Derive directly from entity mutations
1628    Entity { name: String },
1629    /// Derive from another view's output
1630    View { id: String },
1631}
1632
1633/// Output mode for a view
1634#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
1635pub enum ViewOutput {
1636    /// Multiple entities (list-like semantics)
1637    #[default]
1638    Collection,
1639    /// Single entity (state-like semantics)
1640    Single,
1641    /// Keyed lookup by a specific field
1642    Keyed { key_field: FieldPath },
1643}
1644
1645/// Definition of a view in the pipeline
1646#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1647pub struct ViewDef {
1648    /// Unique view identifier (e.g., "OreRound/latest")
1649    pub id: String,
1650
1651    /// Source this view derives from
1652    pub source: ViewSource,
1653
1654    /// Pipeline of transforms to apply (in order)
1655    #[serde(default)]
1656    pub pipeline: Vec<ViewTransform>,
1657
1658    /// Output mode for this view
1659    #[serde(default)]
1660    pub output: ViewOutput,
1661}
1662
1663impl ViewDef {
1664    /// Create a new list view for an entity
1665    pub fn list(entity_name: &str) -> Self {
1666        ViewDef {
1667            id: format!("{}/list", entity_name),
1668            source: ViewSource::Entity {
1669                name: entity_name.to_string(),
1670            },
1671            pipeline: vec![],
1672            output: ViewOutput::Collection,
1673        }
1674    }
1675
1676    /// Create a new state view for an entity
1677    pub fn state(entity_name: &str, key_field: &[&str]) -> Self {
1678        ViewDef {
1679            id: format!("{}/state", entity_name),
1680            source: ViewSource::Entity {
1681                name: entity_name.to_string(),
1682            },
1683            pipeline: vec![],
1684            output: ViewOutput::Keyed {
1685                key_field: FieldPath::new(key_field),
1686            },
1687        }
1688    }
1689
1690    /// Check if this view produces a single entity
1691    pub fn is_single(&self) -> bool {
1692        matches!(self.output, ViewOutput::Single)
1693    }
1694
1695    /// Check if any transform in the pipeline produces a single result
1696    pub fn has_single_transform(&self) -> bool {
1697        self.pipeline.iter().any(|t| {
1698            matches!(
1699                t,
1700                ViewTransform::First
1701                    | ViewTransform::Last
1702                    | ViewTransform::MaxBy { .. }
1703                    | ViewTransform::MinBy { .. }
1704            )
1705        })
1706    }
1707}
1708
1709#[macro_export]
1710macro_rules! define_accessor {
1711    ($name:ident, $state:ty, $path:expr) => {
1712        pub struct $name;
1713
1714        impl $crate::ast::FieldAccessor<$state> for $name {
1715            fn path(&self) -> String {
1716                $path.to_string()
1717            }
1718        }
1719    };
1720}