stemedb/sdk/go/steme/assertion.go
jordan 1ce4004807 feat: Complete Phase 2 (The Cortex) - query, lens, and API layers
This commit adds the read path (Cortex) to complement the write path (Spine):

## Crates
- stemedb-api: HTTP API with axum + utoipa OpenAPI
  - /v1/assert, /v1/query, /v1/epoch, /v1/skeptic, /v1/trace, /v1/audit
  - Metered endpoints with quota enforcement
  - Ed25519 signature verification
- stemedb-lens: Truth resolution lenses
  - RecencyLens, ConsensusLens, ConfidenceLens
  - VoteAwareConsensusLens (Ballot Box pattern)
  - TrustAwareAuthorityLens (The Hive pattern)
  - SkepticLens (conflict analysis)
  - EpochAwareLens (paradigm-safe queries)
- stemedb-query: Query engine with materialized views

## Storage Extensions
- VoteStore: Vote aggregation with cached counts
- TrustRankStore: Agent reputation with decay
- AuditStore: Query audit trail
- IndexStore: SP/P/S index structures
- SupersessionStore: Epoch supersession chains

## SDKs
- sdk/go/steme: Go HTTP client with Ed25519 signing
- sdk/go/adk: ADK-Go tools for AI agents

## Documentation
- Updated CLAUDE.md, architecture.md, roadmap.md
- New ai-lookup entries for all services
- Use case docs for consumer health intelligence
- Arena roadmap for simulation advancement

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-01 13:22:44 -07:00

272 lines
7.4 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"`
}
// 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
}
// 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
}