stemedb/sdk/go/steme/assertion.go
jordan ad07a75d0a feat: add source content to source registry, signed assertions, feed endpoint, dashboard enhancements
- Add `content: Option<String>` to SourceRecord with rkyv schema evolution
  (LegacySourceRecord compat deserializer for backward compatibility)
- Add MAX_SOURCE_CONTENT_LEN (1MB) limit with API validation
- Strip content from list responses, include in single-source GET
- Update Go SDK RegisterSourceRequest with Content field
- FCM pipeline extracts PDF text via pdftotext and passes to registration
- Dashboard impact panel fetches and displays source content with expand/collapse
- Add feed endpoint, dashboard feed panel, and signed assertion support
- Update data-structures.md, API docs, and storage docs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 21:54:27 -07:00

281 lines
7.7 KiB
Go

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
}