package steme import ( "encoding/hex" "fmt" ) // Assertion represents a knowledge graph assertion. // // This is the primary type for building assertions to submit to StemeDB. // Use the fluent builder API with NewAssertion() for ergonomic construction. type Assertion struct { // The subject entity (e.g., "Tesla_Inc") Subject string `json:"subject"` // The predicate/relation (e.g., "has_revenue") Predicate string `json:"predicate"` // The object value Object ObjectValue `json:"object"` // Confidence score (0.0 to 1.0) Confidence float64 `json:"confidence"` // Agent signatures vouching for this assertion Signatures []SignatureEntry `json:"signatures"` // Hash of parent assertion (hex-encoded, optional for forks) ParentHash *string `json:"parent_hash,omitempty"` // Hash of source evidence (hex-encoded) SourceHash string `json:"source_hash"` // Source authority tier (defaults to Expert if not specified) SourceClass *SourceClass `json:"source_class,omitempty"` // Perceptual hash for visual anchoring (hex-encoded, optional) VisualHash *string `json:"visual_hash,omitempty"` // Epoch ID (hex-encoded, optional) Epoch *string `json:"epoch,omitempty"` // Lifecycle stage (defaults to Proposed if not specified) Lifecycle *LifecycleStage `json:"lifecycle,omitempty"` // Semantic embedding vector (optional) Vector []float32 `json:"vector,omitempty"` // Free-text narrative explaining methodology, limitations, bias, and caveats (optional) Narrative *string `json:"narrative,omitempty"` } // AssertionBuilder provides a fluent API for building assertions. type AssertionBuilder struct { assertion Assertion } // NewAssertion creates a new assertion builder with required fields. // // The subject and predicate are required. All other fields can be set // via builder methods. // // Example: // // assertion := steme.NewAssertion("Tesla_Inc", "has_revenue"). // WithNumber(96.7). // WithConfidence(0.95). // WithLifecycle(steme.LifecycleApproved). // WithSourceClass(steme.SourceClassClinical) func NewAssertion(subject, predicate string) *AssertionBuilder { return &AssertionBuilder{ assertion: Assertion{ Subject: subject, Predicate: predicate, Confidence: 1.0, // Default to full confidence }, } } // WithText sets the object to a text value. func (b *AssertionBuilder) WithText(text string) *AssertionBuilder { b.assertion.Object = NewTextValue(text) return b } // WithNumber sets the object to a numeric value. func (b *AssertionBuilder) WithNumber(num float64) *AssertionBuilder { b.assertion.Object = NewNumberValue(num) return b } // WithBoolean sets the object to a boolean value. func (b *AssertionBuilder) WithBoolean(val bool) *AssertionBuilder { b.assertion.Object = NewBooleanValue(val) return b } // WithReference sets the object to an entity reference. func (b *AssertionBuilder) WithReference(entityID string) *AssertionBuilder { b.assertion.Object = NewReferenceValue(entityID) return b } // WithObject sets the object value directly. func (b *AssertionBuilder) WithObject(obj ObjectValue) *AssertionBuilder { b.assertion.Object = obj return b } // WithConfidence sets the confidence score (0.0 to 1.0). func (b *AssertionBuilder) WithConfidence(confidence float64) *AssertionBuilder { b.assertion.Confidence = confidence return b } // WithSourceHash sets the source evidence hash (hex-encoded, 32 bytes). func (b *AssertionBuilder) WithSourceHash(hashHex string) *AssertionBuilder { b.assertion.SourceHash = hashHex return b } // WithSourceClass sets the source authority tier. func (b *AssertionBuilder) WithSourceClass(sc SourceClass) *AssertionBuilder { b.assertion.SourceClass = &sc return b } // WithLifecycle sets the lifecycle stage. func (b *AssertionBuilder) WithLifecycle(lifecycle LifecycleStage) *AssertionBuilder { b.assertion.Lifecycle = &lifecycle return b } // WithParentHash sets the parent assertion hash (hex-encoded, 32 bytes). // // This is used for forking existing assertions. func (b *AssertionBuilder) WithParentHash(hashHex string) *AssertionBuilder { b.assertion.ParentHash = &hashHex return b } // WithVisualHash sets the perceptual hash (hex-encoded, 8 bytes). // // This is used for visual anchoring (e.g., screenshots of tables). func (b *AssertionBuilder) WithVisualHash(hashHex string) *AssertionBuilder { b.assertion.VisualHash = &hashHex return b } // WithEpoch sets the epoch ID (hex-encoded, 32 bytes). func (b *AssertionBuilder) WithEpoch(epochHex string) *AssertionBuilder { b.assertion.Epoch = &epochHex return b } // WithNarrative sets the free-text narrative (methodology, limitations, caveats). func (b *AssertionBuilder) WithNarrative(narrative string) *AssertionBuilder { b.assertion.Narrative = &narrative return b } // WithVector sets the semantic embedding vector. func (b *AssertionBuilder) WithVector(vector []float32) *AssertionBuilder { b.assertion.Vector = vector return b } // WithSignatures sets the signature entries directly. // // This is used if you've pre-computed signatures. Most users should // let the Client sign automatically. func (b *AssertionBuilder) WithSignatures(sigs []SignatureEntry) *AssertionBuilder { b.assertion.Signatures = sigs return b } // Build returns the constructed Assertion. // // This does NOT validate the assertion. Validation happens on the server. func (b *AssertionBuilder) Build() Assertion { return b.assertion } // Validate checks the assertion for common errors. // // Returns an error if: // - Confidence is not in [0.0, 1.0] // - Object value is not set // - SourceHash is empty // - Hex-encoded hashes have invalid lengths func (a *Assertion) Validate() error { if a.Confidence < 0.0 || a.Confidence > 1.0 { return &ValidationError{ Field: "confidence", Message: fmt.Sprintf("must be between 0.0 and 1.0, got %f", a.Confidence), } } if a.Object.Type == "" { return &ValidationError{ Field: "object", Message: "object value must be set", } } if a.SourceHash == "" { return &ValidationError{ Field: "source_hash", Message: "source_hash is required", } } // Validate source_hash is 64 hex chars (32 bytes) if len(a.SourceHash) != 64 { return &ValidationError{ Field: "source_hash", Message: fmt.Sprintf("must be 64 hex characters (32 bytes), got %d", len(a.SourceHash)), } } if _, err := hex.DecodeString(a.SourceHash); err != nil { return &ValidationError{ Field: "source_hash", Message: fmt.Sprintf("invalid hex encoding: %v", err), } } // Validate parent_hash if present if a.ParentHash != nil { if len(*a.ParentHash) != 64 { return &ValidationError{ Field: "parent_hash", Message: fmt.Sprintf("must be 64 hex characters (32 bytes), got %d", len(*a.ParentHash)), } } if _, err := hex.DecodeString(*a.ParentHash); err != nil { return &ValidationError{ Field: "parent_hash", Message: fmt.Sprintf("invalid hex encoding: %v", err), } } } // Validate visual_hash if present (8 bytes = 16 hex chars) if a.VisualHash != nil { if len(*a.VisualHash) != 16 { return &ValidationError{ Field: "visual_hash", Message: fmt.Sprintf("must be 16 hex characters (8 bytes), got %d", len(*a.VisualHash)), } } if _, err := hex.DecodeString(*a.VisualHash); err != nil { return &ValidationError{ Field: "visual_hash", Message: fmt.Sprintf("invalid hex encoding: %v", err), } } } // Validate epoch if present if a.Epoch != nil { if len(*a.Epoch) != 64 { return &ValidationError{ Field: "epoch", Message: fmt.Sprintf("must be 64 hex characters (32 bytes), got %d", len(*a.Epoch)), } } if _, err := hex.DecodeString(*a.Epoch); err != nil { return &ValidationError{ Field: "epoch", Message: fmt.Sprintf("invalid hex encoding: %v", err), } } } return nil }