Major additions: - Community Next.js app (port 18187) for browsing claims with API docs - stemedb-chaos crate: Fault injection, chaos testing, CRDT properties - Latent ingestion system: Reddit/FDA ingesters with ADK-Go agents - Disputed claims handling: Manual review workflows and validation - Aphoria security scanner: New extractors (SQL injection, command injection, weak crypto, TLS version), policy-based ignores, UAT reports - Docker infrastructure: Dockerfile, docker-compose.yml for full stack - VulnBank demo: Intentionally vulnerable multi-language test corpus SDK & API enhancements: - Source registry handlers for tracking data provenance - Metrics endpoint - Skeptic filtering improvements Code quality: - Split 14 large files (>500 lines) into focused modules - All files now under 500-line limit per project guidelines Documentation: - Chaos testing guide, circuit breakers, observability docs - Phase 7 UAT documentation updates - Martin Kleppmann technical writer agent Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
141 lines
4.2 KiB
Go
141 lines
4.2 KiB
Go
// Package main demonstrates basic StemeDB SDK usage.
|
|
//
|
|
// This example shows:
|
|
// - Creating a signer (keypair)
|
|
// - Building an assertion with the fluent API
|
|
// - Submitting to StemeDB (auto-signed)
|
|
// - Understanding eventual consistency
|
|
// - Querying with a lens
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log"
|
|
"time"
|
|
|
|
"github.com/orchard9/stemedb-go/steme"
|
|
)
|
|
|
|
func main() {
|
|
// 1. Generate a new keypair
|
|
//
|
|
// In production, you'd load this from environment or key management.
|
|
// For this example, we generate a new one each run.
|
|
signer, err := steme.GenerateSigner()
|
|
if err != nil {
|
|
log.Fatalf("Failed to generate signer: %v", err)
|
|
}
|
|
|
|
fmt.Printf("Agent ID (public key): %s\n", signer.PublicKey())
|
|
fmt.Printf("Seed (save this!): %s\n\n", signer.Seed())
|
|
|
|
// 2. Create a StemeDB client
|
|
//
|
|
// The client automatically signs all assertions using the signer.
|
|
client := steme.NewClient("http://localhost:18180", signer)
|
|
|
|
// 3. Build an assertion with the fluent API
|
|
//
|
|
// This asserts: "Tesla_Inc has_revenue 96.7 (billion USD)"
|
|
// with 95% confidence, marked as Approved, sourced from Clinical evidence.
|
|
assertion := steme.NewAssertion("Tesla_Inc", "has_revenue").
|
|
WithNumber(96.7).
|
|
WithConfidence(0.95).
|
|
WithLifecycle(steme.LifecycleApproved).
|
|
WithSourceClass(steme.SourceClassClinical).
|
|
WithSourceHash("0000000000000000000000000000000000000000000000000000000000000000").
|
|
Build()
|
|
|
|
// 4. Submit to StemeDB
|
|
//
|
|
// The client automatically:
|
|
// - Signs the assertion with your private key
|
|
// - Adds a timestamp
|
|
// - POSTs to /v1/assert
|
|
// - Returns the content-addressed hash
|
|
hash, err := client.Assert(context.Background(), assertion)
|
|
if err != nil {
|
|
log.Fatalf("Failed to assert: %v", err)
|
|
}
|
|
|
|
fmt.Printf("✓ Created assertion: %s\n\n", hash)
|
|
|
|
// 5. Understanding Eventual Consistency
|
|
//
|
|
// StemeDB is eventually consistent by design. When Assert() returns,
|
|
// the assertion is durably written to the Write-Ahead Log (WAL), but
|
|
// the background IngestWorker hasn't necessarily indexed it yet.
|
|
//
|
|
// This is intentional for high write throughput. For production code:
|
|
// - Design workflows that tolerate async (recommended for agents)
|
|
// - Use polling with timeout when read-after-write is needed
|
|
// - Check health.AssertionsCount to monitor ingestion progress
|
|
//
|
|
// Here we demonstrate polling until the assertion is queryable:
|
|
|
|
params := steme.NewQuery().
|
|
WithSubject("Tesla_Inc").
|
|
WithPredicate("has_revenue").
|
|
WithLens(steme.LensConsensus).
|
|
Build()
|
|
|
|
fmt.Println("Waiting for ingestion...")
|
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
defer cancel()
|
|
|
|
var result *steme.QueryResult
|
|
for {
|
|
result, err = client.Query(ctx, params)
|
|
if err != nil {
|
|
log.Fatalf("Failed to query: %v", err)
|
|
}
|
|
if result.TotalCount > 0 {
|
|
fmt.Println("✓ Assertion indexed")
|
|
fmt.Println()
|
|
break
|
|
}
|
|
select {
|
|
case <-ctx.Done():
|
|
// Timeout - assertion created but not yet indexed
|
|
// This is normal; in production, design for eventual consistency
|
|
fmt.Println("Note: Assertion created but not yet indexed")
|
|
fmt.Println("This is expected - StemeDB is eventually consistent")
|
|
fmt.Println()
|
|
goto showResults
|
|
case <-time.After(100 * time.Millisecond):
|
|
// Continue polling
|
|
}
|
|
}
|
|
|
|
showResults:
|
|
// 6. Display query results
|
|
fmt.Printf("Query Results:\n")
|
|
fmt.Printf(" Total: %d assertions\n", result.TotalCount)
|
|
fmt.Printf(" Has more: %v\n\n", result.HasMore)
|
|
|
|
for i, a := range result.Assertions {
|
|
fmt.Printf("Assertion #%d:\n", i+1)
|
|
fmt.Printf(" Hash: %s\n", a.Hash)
|
|
fmt.Printf(" Subject: %s\n", a.Subject)
|
|
fmt.Printf(" Predicate: %s\n", a.Predicate)
|
|
fmt.Printf(" Object: %v (%s)\n", a.Object.Value, a.Object.Type)
|
|
fmt.Printf(" Confidence: %.2f\n", a.Confidence)
|
|
fmt.Printf(" Lifecycle: %s\n", a.Lifecycle)
|
|
fmt.Printf(" Source Class: %s\n", a.SourceClass)
|
|
fmt.Printf(" Signatures: %d agents\n", len(a.Signatures))
|
|
fmt.Printf("\n")
|
|
}
|
|
|
|
// 7. Health check
|
|
health, err := client.Health(context.Background())
|
|
if err != nil {
|
|
log.Fatalf("Failed health check: %v", err)
|
|
}
|
|
|
|
fmt.Printf("StemeDB Health:\n")
|
|
fmt.Printf(" Status: %s\n", health.Status)
|
|
fmt.Printf(" Version: %s\n", health.Version)
|
|
fmt.Printf(" Assertions: %d\n", health.AssertionsCount)
|
|
}
|